diff --git a/Gemfile b/Gemfile
index 7e7e6ea97..d1c6c6534 100644
--- a/Gemfile
+++ b/Gemfile
@@ -27,10 +27,10 @@ gem 'uglifier'
gem 'flag_shih_tzu'
# Random name generator for user names
gem 'haikunator'
-# Template parsing safetly
+# Template parsing safely
gem 'liquid'
# Parse Markdown to HTML
-gem 'redcarpet'
+gem 'commonmarker'
##-- for active storage --##
gem 'aws-sdk-s3', require: false
@@ -44,6 +44,8 @@ gem 'pg'
gem 'redis'
gem 'redis-namespace'
gem 'redis-rack-cache'
+# super fast record imports in bulk
+gem 'activerecord-import'
##--- gems for server & infra configuration ---##
gem 'dotenv-rails'
diff --git a/Gemfile.lock b/Gemfile.lock
index d9da65980..5dbad5245 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -18,56 +18,58 @@ GEM
specs:
action-cable-testing (0.6.1)
actioncable (>= 5.0)
- actioncable (6.0.3.4)
- actionpack (= 6.0.3.4)
+ actioncable (6.0.3.5)
+ actionpack (= 6.0.3.5)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
- actionmailbox (6.0.3.4)
- actionpack (= 6.0.3.4)
- activejob (= 6.0.3.4)
- activerecord (= 6.0.3.4)
- activestorage (= 6.0.3.4)
- activesupport (= 6.0.3.4)
+ actionmailbox (6.0.3.5)
+ actionpack (= 6.0.3.5)
+ activejob (= 6.0.3.5)
+ activerecord (= 6.0.3.5)
+ activestorage (= 6.0.3.5)
+ activesupport (= 6.0.3.5)
mail (>= 2.7.1)
- actionmailer (6.0.3.4)
- actionpack (= 6.0.3.4)
- actionview (= 6.0.3.4)
- activejob (= 6.0.3.4)
+ actionmailer (6.0.3.5)
+ actionpack (= 6.0.3.5)
+ actionview (= 6.0.3.5)
+ activejob (= 6.0.3.5)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (6.0.3.4)
- actionview (= 6.0.3.4)
- activesupport (= 6.0.3.4)
+ actionpack (6.0.3.5)
+ actionview (= 6.0.3.5)
+ activesupport (= 6.0.3.5)
rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
- actiontext (6.0.3.4)
- actionpack (= 6.0.3.4)
- activerecord (= 6.0.3.4)
- activestorage (= 6.0.3.4)
- activesupport (= 6.0.3.4)
+ actiontext (6.0.3.5)
+ actionpack (= 6.0.3.5)
+ activerecord (= 6.0.3.5)
+ activestorage (= 6.0.3.5)
+ activesupport (= 6.0.3.5)
nokogiri (>= 1.8.5)
- actionview (6.0.3.4)
- activesupport (= 6.0.3.4)
+ actionview (6.0.3.5)
+ activesupport (= 6.0.3.5)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
- activejob (6.0.3.4)
- activesupport (= 6.0.3.4)
+ activejob (6.0.3.5)
+ activesupport (= 6.0.3.5)
globalid (>= 0.3.6)
- activemodel (6.0.3.4)
- activesupport (= 6.0.3.4)
- activerecord (6.0.3.4)
- activemodel (= 6.0.3.4)
- activesupport (= 6.0.3.4)
- activestorage (6.0.3.4)
- actionpack (= 6.0.3.4)
- activejob (= 6.0.3.4)
- activerecord (= 6.0.3.4)
+ activemodel (6.0.3.5)
+ activesupport (= 6.0.3.5)
+ activerecord (6.0.3.5)
+ activemodel (= 6.0.3.5)
+ activesupport (= 6.0.3.5)
+ activerecord-import (1.0.7)
+ activerecord (>= 3.2)
+ activestorage (6.0.3.5)
+ actionpack (= 6.0.3.5)
+ activejob (= 6.0.3.5)
+ activerecord (= 6.0.3.5)
marcel (~> 0.3.1)
- activesupport (6.0.3.4)
+ activesupport (6.0.3.5)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@@ -145,7 +147,9 @@ GEM
coderay (1.1.3)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
- concurrent-ruby (1.1.7)
+ commonmarker (0.21.1)
+ ruby-enum (~> 0.5)
+ concurrent-ruby (1.1.8)
connection_pool (2.2.3)
crack (0.4.3)
safe_yaml (~> 1.0.0)
@@ -181,7 +185,7 @@ GEM
dotenv (= 2.7.6)
railties (>= 3.2)
equalizer (0.0.11)
- erubi (1.9.0)
+ erubi (1.10.0)
et-orbi (1.2.4)
tzinfo
execjs (2.7.0)
@@ -257,7 +261,7 @@ GEM
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
- i18n (1.8.5)
+ i18n (1.8.8)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
inflecto (0.0.2)
@@ -294,7 +298,7 @@ GEM
listen (3.3.3)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
- loofah (2.7.0)
+ loofah (2.9.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@@ -311,7 +315,7 @@ GEM
mini_magick (4.10.1)
mini_mime (1.0.2)
mini_portile2 (2.5.0)
- minitest (5.14.2)
+ minitest (5.14.3)
momentjs-rails (2.20.1)
railties (>= 3.1)
msgpack (1.3.3)
@@ -321,8 +325,8 @@ GEM
net-http-persistent (4.0.0)
connection_pool (~> 2.2)
netrc (0.11.0)
- nio4r (2.5.4)
- nokogiri (1.11.0)
+ nio4r (2.5.5)
+ nokogiri (1.11.1)
mini_portile2 (~> 2.5.0)
racc (~> 1.4)
oauth (0.5.4)
@@ -353,38 +357,37 @@ GEM
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
- rails (6.0.3.4)
- actioncable (= 6.0.3.4)
- actionmailbox (= 6.0.3.4)
- actionmailer (= 6.0.3.4)
- actionpack (= 6.0.3.4)
- actiontext (= 6.0.3.4)
- actionview (= 6.0.3.4)
- activejob (= 6.0.3.4)
- activemodel (= 6.0.3.4)
- activerecord (= 6.0.3.4)
- activestorage (= 6.0.3.4)
- activesupport (= 6.0.3.4)
+ rails (6.0.3.5)
+ actioncable (= 6.0.3.5)
+ actionmailbox (= 6.0.3.5)
+ actionmailer (= 6.0.3.5)
+ actionpack (= 6.0.3.5)
+ actiontext (= 6.0.3.5)
+ actionview (= 6.0.3.5)
+ activejob (= 6.0.3.5)
+ activemodel (= 6.0.3.5)
+ activerecord (= 6.0.3.5)
+ activestorage (= 6.0.3.5)
+ activesupport (= 6.0.3.5)
bundler (>= 1.3.0)
- railties (= 6.0.3.4)
+ railties (= 6.0.3.5)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.3.0)
loofah (~> 2.3)
- railties (6.0.3.4)
- actionpack (= 6.0.3.4)
- activesupport (= 6.0.3.4)
+ railties (6.0.3.5)
+ actionpack (= 6.0.3.5)
+ activesupport (= 6.0.3.5)
method_source
rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0)
rainbow (3.0.0)
- rake (13.0.1)
+ rake (13.0.3)
rb-fsevent (0.10.4)
rb-inotify (0.10.1)
ffi (~> 1.0)
- redcarpet (3.5.1)
redis (4.2.1)
redis-namespace (1.8.0)
redis (>= 3.0.4)
@@ -444,6 +447,8 @@ GEM
rubocop (>= 0.87.0)
rubocop-rspec (1.43.2)
rubocop (~> 0.87)
+ ruby-enum (0.9.0)
+ i18n
ruby-progressbar (1.10.1)
ruby2ruby (2.4.4)
ruby_parser (~> 3.1)
@@ -520,7 +525,7 @@ GEM
inflecto
virtus
telephone_number (1.4.9)
- thor (1.0.1)
+ thor (1.1.0)
thread_safe (0.3.6)
tilt (2.0.10)
time_diff (0.3.0)
@@ -532,7 +537,7 @@ GEM
nokogiri (>= 1.6, < 2.0)
twitty (0.1.1)
oauth
- tzinfo (1.2.8)
+ tzinfo (1.2.9)
thread_safe (~> 0.1)
tzinfo-data (1.2020.1)
tzinfo (>= 1.0.0)
@@ -575,13 +580,14 @@ GEM
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
wisper (2.0.0)
- zeitwerk (2.4.1)
+ zeitwerk (2.4.2)
PLATFORMS
ruby
DEPENDENCIES
action-cable-testing
+ activerecord-import
acts-as-taggable-on
administrate
annotate
@@ -595,6 +601,7 @@ DEPENDENCIES
bullet
bundle-audit
byebug
+ commonmarker
cypress-on-rails (~> 1.0)
database_cleaner
devise
@@ -629,7 +636,6 @@ DEPENDENCIES
pundit
rack-cors
rails
- redcarpet
redis
redis-namespace
redis-rack-cache
diff --git a/app/controllers/api/v1/accounts/contacts/contact_inboxes_controller.rb b/app/controllers/api/v1/accounts/contacts/contact_inboxes_controller.rb
index 7875b0f10..16686dbfe 100644
--- a/app/controllers/api/v1/accounts/contacts/contact_inboxes_controller.rb
+++ b/app/controllers/api/v1/accounts/contacts/contact_inboxes_controller.rb
@@ -1,21 +1,14 @@
class Api::V1::Accounts::Contacts::ContactInboxesController < Api::V1::Accounts::BaseController
before_action :ensure_contact
before_action :ensure_inbox, only: [:create]
- before_action :validate_channel_type
def create
source_id = params[:source_id] || SecureRandom.uuid
- @contact_inbox = ContactInbox.create(contact: @contact, inbox: @inbox, source_id: source_id)
+ @contact_inbox = ContactInbox.create!(contact: @contact, inbox: @inbox, source_id: source_id)
end
private
- def validate_channel_type
- return if @inbox.channel_type == 'Channel::Api'
-
- render json: { error: 'Contact Inbox creation is only allowed in API inboxes' }, status: :unprocessable_entity
- end
-
def ensure_inbox
@inbox = Current.account.inboxes.find(params[:inbox_id])
end
diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb
index 2ea843f9b..37a583f05 100644
--- a/app/controllers/api/v1/accounts/contacts_controller.rb
+++ b/app/controllers/api/v1/accounts/contacts_controller.rb
@@ -22,6 +22,14 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
@contacts = fetch_contact_last_seen_at(contacts)
end
+ def import
+ ActiveRecord::Base.transaction do
+ import = Current.account.data_imports.create!(data_type: 'contacts')
+ import.import_file.attach(params[:import_file])
+ end
+ head :ok
+ end
+
# returns online contacts
def active
contacts = Current.account.contacts.where(id: ::OnlineStatusTracker
@@ -46,7 +54,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
rescue ActiveRecord::RecordInvalid => e
render json: {
message: e.record.errors.full_messages.join(', '),
- contact: Contact.find_by(email: contact_params[:email])
+ contact: Current.account.contacts.find_by(email: contact_params[:email])
}, status: :unprocessable_entity
end
diff --git a/app/controllers/api/v1/accounts/conversations/assignments_controller.rb b/app/controllers/api/v1/accounts/conversations/assignments_controller.rb
index 58fbc5b4e..670abf37a 100644
--- a/app/controllers/api/v1/accounts/conversations/assignments_controller.rb
+++ b/app/controllers/api/v1/accounts/conversations/assignments_controller.rb
@@ -1,9 +1,20 @@
class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Accounts::Conversations::BaseController
- # assign agent to a conversation
+ # assigns agent/team to a conversation
def create
+ set_assignee
+ render json: @assignee
+ end
+
+ private
+
+ def set_assignee
# if params[:assignee_id] is not a valid id, it will set to nil, hence unassigning the conversation
- assignee = Current.account.users.find_by(id: params[:assignee_id])
- @conversation.update_assignee(assignee)
- render json: assignee
+ if params.key?(:assignee_id)
+ @assignee = Current.account.users.find_by(id: params[:assignee_id])
+ @conversation.update_assignee(@assignee)
+ elsif params.key?(:team_id)
+ @assignee = Current.account.teams.find_by(id: params[:team_id])
+ @conversation.update!(team: @assignee)
+ end
end
end
diff --git a/app/controllers/api/v1/accounts/inbox_members_controller.rb b/app/controllers/api/v1/accounts/inbox_members_controller.rb
index fc960d9ec..f715c7040 100644
--- a/app/controllers/api/v1/accounts/inbox_members_controller.rb
+++ b/app/controllers/api/v1/accounts/inbox_members_controller.rb
@@ -22,9 +22,10 @@ class Api::V1::Accounts::InboxMembersController < Api::V1::Accounts::BaseControl
# get the list of user_ids from params
# the missing ones are the agents which are to be deleted from the inbox
# the new ones are the agents which are to be added to the inbox
-
- agents_to_be_added_ids.each { |user_id| @inbox.add_member(user_id) }
- agents_to_be_removed_ids.each { |user_id| @inbox.remove_member(user_id) }
+ ActiveRecord::Base.transaction do
+ agents_to_be_added_ids.each { |user_id| @inbox.add_member(user_id) }
+ agents_to_be_removed_ids.each { |user_id| @inbox.remove_member(user_id) }
+ end
end
def agents_to_be_added_ids
diff --git a/app/controllers/api/v1/accounts/inboxes_controller.rb b/app/controllers/api/v1/accounts/inboxes_controller.rb
index 0f573bbb9..6c078d28f 100644
--- a/app/controllers/api/v1/accounts/inboxes_controller.rb
+++ b/app/controllers/api/v1/accounts/inboxes_controller.rb
@@ -89,6 +89,8 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
:webhook_url,
:email,
:reply_time,
+ :pre_chat_form_enabled,
+ { pre_chat_form_options: [:pre_chat_message, :require_email] },
{ selected_feature_flags: [] }
])
end
diff --git a/app/controllers/api/v1/accounts/notifications_controller.rb b/app/controllers/api/v1/accounts/notifications_controller.rb
index 49191caad..8f63d79f5 100644
--- a/app/controllers/api/v1/accounts/notifications_controller.rb
+++ b/app/controllers/api/v1/accounts/notifications_controller.rb
@@ -1,6 +1,7 @@
class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseController
- protect_from_forgery with: :null_session
+ RESULTS_PER_PAGE = 15
+ protect_from_forgery with: :null_session
before_action :fetch_notification, only: [:update]
before_action :set_primary_actor, only: [:read_all]
before_action :set_current_page, only: [:index]
@@ -8,17 +9,18 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro
def index
@unread_count = current_user.notifications.where(account_id: current_account.id, read_at: nil).count
@count = notifications.count
- @notifications = notifications.page @current_page
+ @notifications = notifications.page(@current_page).per(RESULTS_PER_PAGE)
end
def read_all
+ # rubocop:disable Rails/SkipsModelValidations
if @primary_actor
current_user.notifications.where(account_id: current_account.id, primary_actor: @primary_actor, read_at: nil)
- .update(read_at: DateTime.now.utc)
+ .update_all(read_at: DateTime.now.utc)
else
- current_user.notifications.where(account_id: current_account.id, read_at: nil).update(read_at: DateTime.now.utc)
+ current_user.notifications.where(account_id: current_account.id, read_at: nil).update_all(read_at: DateTime.now.utc)
end
-
+ # rubocop:enable Rails/SkipsModelValidations
head :ok
end
diff --git a/app/controllers/api/v1/accounts/team_members_controller.rb b/app/controllers/api/v1/accounts/team_members_controller.rb
index 32a792509..d7e17a687 100644
--- a/app/controllers/api/v1/accounts/team_members_controller.rb
+++ b/app/controllers/api/v1/accounts/team_members_controller.rb
@@ -7,17 +7,41 @@ class Api::V1::Accounts::TeamMembersController < Api::V1::Accounts::BaseControll
end
def create
- record = @team.team_members.find_or_create_by(user_id: params[:user_id])
- @team_member = record.user
+ ActiveRecord::Base.transaction do
+ @team_members = params[:user_ids].map { |user_id| @team.add_member(user_id) }
+ end
+ end
+
+ def update
+ ActiveRecord::Base.transaction do
+ members_to_be_added_ids.each { |user_id| @team.add_member(user_id) }
+ members_to_be_removed_ids.each { |user_id| @team.remove_member(user_id) }
+ end
+ @team_members = @team.members
+ render action: 'create'
end
def destroy
- @team.team_members.find_by(user_id: params[:user_id])&.destroy
+ ActiveRecord::Base.transaction do
+ params[:user_ids].map { |user_id| @team.remove_member(user_id) }
+ end
head :ok
end
private
+ def members_to_be_added_ids
+ params[:user_ids] - current_members_ids
+ end
+
+ def members_to_be_removed_ids
+ current_members_ids - params[:user_ids]
+ end
+
+ def current_members_ids
+ @current_members_ids ||= @team.members.pluck(:id)
+ end
+
def fetch_team
@team = Current.account.teams.find(params[:team_id])
end
diff --git a/app/controllers/api/v1/accounts/twitter/authorizations_controller.rb b/app/controllers/api/v1/accounts/twitter/authorizations_controller.rb
new file mode 100644
index 000000000..f96a2921c
--- /dev/null
+++ b/app/controllers/api/v1/accounts/twitter/authorizations_controller.rb
@@ -0,0 +1,29 @@
+class Api::V1::Accounts::Twitter::AuthorizationsController < Api::V1::Accounts::BaseController
+ include TwitterConcern
+
+ before_action :check_authorization
+
+ def create
+ @response = twitter_client.request_oauth_token(url: twitter_callback_url)
+ if @response.status == '200'
+ ::Redis::Alfred.setex(oauth_token, Current.account.id)
+ render json: { success: true, url: oauth_authorize_endpoint(oauth_token) }
+ else
+ render json: { success: false }, status: :unprocessable_entity
+ end
+ end
+
+ private
+
+ def oauth_token
+ parsed_body['oauth_token']
+ end
+
+ def oauth_authorize_endpoint(oauth_token)
+ "#{twitter_api_base_url}/oauth/authorize?oauth_token=#{oauth_token}"
+ end
+
+ def check_authorization
+ raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
+ end
+end
diff --git a/app/controllers/api/v1/widget/base_controller.rb b/app/controllers/api/v1/widget/base_controller.rb
index 1d377d69d..52f19595e 100644
--- a/app/controllers/api/v1/widget/base_controller.rb
+++ b/app/controllers/api/v1/widget/base_controller.rb
@@ -20,11 +20,7 @@ class Api::V1::Widget::BaseController < ApplicationController
end
def auth_token_params
- @auth_token_params ||= ::Widget::TokenService.new(token: request.headers[header_name]).decode_token
- end
-
- def header_name
- 'X-Auth-Token'
+ @auth_token_params ||= ::Widget::TokenService.new(token: request.headers['X-Auth-Token']).decode_token
end
def set_web_widget
@@ -39,6 +35,50 @@ class Api::V1::Widget::BaseController < ApplicationController
@contact = @contact_inbox.contact
end
+ def create_conversation
+ ::Conversation.create!(conversation_params)
+ end
+
+ def inbox
+ @inbox ||= ::Inbox.find_by(id: auth_token_params[:inbox_id])
+ end
+
+ def conversation_params
+ # FIXME: typo referrer in additional attributes, will probably require a migration.
+ {
+ account_id: inbox.account_id,
+ inbox_id: inbox.id,
+ contact_id: @contact.id,
+ contact_inbox_id: @contact_inbox.id,
+ additional_attributes: {
+ browser: browser_params,
+ referer: permitted_params[:message][:referer_url],
+ initiated_at: timestamp_params
+ }
+ }
+ end
+
+ def update_contact(email)
+ contact_with_email = @current_account.contacts.find_by(email: email)
+ if contact_with_email
+ @contact = ::ContactMergeAction.new(
+ account: @current_account,
+ base_contact: contact_with_email,
+ mergee_contact: @contact
+ ).perform
+ else
+ @contact.update!(email: email, name: contact_name)
+ end
+ end
+
+ def contact_email
+ permitted_params[:contact][:email].downcase
+ end
+
+ def contact_name
+ params[:contact][:name] || contact_email.split('@')[0]
+ end
+
def browser_params
{
browser_name: browser.name,
@@ -48,4 +88,19 @@ class Api::V1::Widget::BaseController < ApplicationController
platform_version: browser.platform.version
}
end
+
+ def timestamp_params
+ { timestamp: permitted_params[:message][:timestamp] }
+ end
+
+ def message_params
+ {
+ account_id: conversation.account_id,
+ sender: @contact,
+ content: permitted_params[:message][:content],
+ inbox_id: conversation.inbox_id,
+ echo_id: permitted_params[:message][:echo_id],
+ message_type: :incoming
+ }
+ end
end
diff --git a/app/controllers/api/v1/widget/contacts_controller.rb b/app/controllers/api/v1/widget/contacts_controller.rb
index 1fca2ee81..ad818d7ac 100644
--- a/app/controllers/api/v1/widget/contacts_controller.rb
+++ b/app/controllers/api/v1/widget/contacts_controller.rb
@@ -12,7 +12,7 @@ class Api::V1::Widget::ContactsController < Api::V1::Widget::BaseController
def process_hmac
return if params[:identifier_hash].blank?
- raise StandardError, 'HMAC failed: Invalid Identifer Hash Provided' unless valid_hmac?
+ raise StandardError, 'HMAC failed: Invalid Identifier Hash Provided' unless valid_hmac?
@contact_inbox.update(hmac_verified: true)
end
diff --git a/app/controllers/api/v1/widget/conversations_controller.rb b/app/controllers/api/v1/widget/conversations_controller.rb
index bafd008f9..cc68cb888 100644
--- a/app/controllers/api/v1/widget/conversations_controller.rb
+++ b/app/controllers/api/v1/widget/conversations_controller.rb
@@ -5,6 +5,14 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
@conversation = conversation
end
+ def create
+ ActiveRecord::Base.transaction do
+ update_contact(contact_email) if @contact.email.blank? && contact_email.present?
+ @conversation = create_conversation
+ conversation.messages.create(message_params)
+ end
+ end
+
def update_last_seen
head :ok && return if conversation.nil?
@@ -43,6 +51,6 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
end
def permitted_params
- params.permit(:id, :typing_status, :website_token, :email)
+ params.permit(:id, :typing_status, :website_token, :email, contact: [:name, :email], message: [:content, :referer_url, :timestamp, :echo_id])
end
end
diff --git a/app/controllers/api/v1/widget/messages_controller.rb b/app/controllers/api/v1/widget/messages_controller.rb
index c98138366..6def00a3a 100644
--- a/app/controllers/api/v1/widget/messages_controller.rb
+++ b/app/controllers/api/v1/widget/messages_controller.rb
@@ -39,44 +39,7 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
end
def set_conversation
- @conversation = ::Conversation.create!(conversation_params) if conversation.nil?
- end
-
- def message_params
- {
- account_id: conversation.account_id,
- sender: @contact,
- content: permitted_params[:message][:content],
- inbox_id: conversation.inbox_id,
- echo_id: permitted_params[:message][:echo_id],
- message_type: :incoming
- }
- end
-
- def conversation_params
- # FIXME: typo referrer in additional attributes
- # will probably require a migration.
- {
- account_id: inbox.account_id,
- inbox_id: inbox.id,
- contact_id: @contact.id,
- contact_inbox_id: @contact_inbox.id,
- additional_attributes: {
- browser: browser_params,
- referer: permitted_params[:message][:referer_url],
- initiated_at: timestamp_params
- }
- }
- end
-
- def timestamp_params
- {
- timestamp: permitted_params[:message][:timestamp]
- }
- end
-
- def inbox
- @inbox ||= ::Inbox.find_by(id: auth_token_params[:inbox_id])
+ @conversation = create_conversation if conversation.nil?
end
def message_finder_params
@@ -90,36 +53,12 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
@message_finder ||= MessageFinder.new(conversation, message_finder_params)
end
- def update_contact(email)
- contact_with_email = @current_account.contacts.find_by(email: email)
- if contact_with_email
- @contact = ::ContactMergeAction.new(
- account: @current_account,
- base_contact: contact_with_email,
- mergee_contact: @contact
- ).perform
- else
- @contact.update!(
- email: email,
- name: contact_name
- )
- end
- end
-
- def contact_email
- permitted_params[:contact][:email].downcase
- end
-
- def contact_name
- contact_email.split('@')[0]
- end
-
def message_update_params
params.permit(message: [{ submitted_values: [:name, :title, :value] }])
end
def permitted_params
- params.permit(:id, :before, :website_token, contact: [:email], message: [:content, :referer_url, :timestamp, :echo_id])
+ params.permit(:id, :before, :website_token, contact: [:name, :email], message: [:content, :referer_url, :timestamp, :echo_id])
end
def set_message
diff --git a/app/controllers/concerns/twitter_concern.rb b/app/controllers/concerns/twitter_concern.rb
new file mode 100644
index 000000000..e2c236d25
--- /dev/null
+++ b/app/controllers/concerns/twitter_concern.rb
@@ -0,0 +1,26 @@
+module TwitterConcern
+ extend ActiveSupport::Concern
+
+ private
+
+ def parsed_body
+ @parsed_body ||= Rack::Utils.parse_nested_query(@response.raw_response.body)
+ end
+
+ def host
+ ENV.fetch('FRONTEND_URL', '')
+ end
+
+ def twitter_client
+ Twitty::Facade.new do |config|
+ config.consumer_key = ENV.fetch('TWITTER_CONSUMER_KEY', nil)
+ config.consumer_secret = ENV.fetch('TWITTER_CONSUMER_SECRET', nil)
+ config.base_url = twitter_api_base_url
+ config.environment = ENV.fetch('TWITTER_ENVIRONMENT', '')
+ end
+ end
+
+ def twitter_api_base_url
+ 'https://api.twitter.com'
+ end
+end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 9ed9f7f47..14030b565 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -20,7 +20,8 @@ class DashboardController < ActionController::Base
'TERMS_URL',
'PRIVACY_URL',
'DISPLAY_MANIFEST',
- 'CREATE_NEW_ACCOUNT_FROM_DASHBOARD'
+ 'CREATE_NEW_ACCOUNT_FROM_DASHBOARD',
+ 'CHATWOOT_INBOX_TOKEN'
).merge(
APP_VERSION: Chatwoot.config[:version]
)
diff --git a/app/controllers/twitter/authorizations_controller.rb b/app/controllers/twitter/authorizations_controller.rb
deleted file mode 100644
index 765e026fe..000000000
--- a/app/controllers/twitter/authorizations_controller.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-class Twitter::AuthorizationsController < Twitter::BaseController
- def create
- @response = twitter_client.request_oauth_token(url: twitter_callback_url)
-
- if @response.status == '200'
- ::Redis::Alfred.setex(oauth_token, account.id)
- redirect_to oauth_authorize_endpoint(oauth_token)
- else
- redirect_to app_new_twitter_inbox_url(account_id: account.id)
- end
- end
-
- private
-
- def oauth_token
- parsed_body['oauth_token']
- end
-
- def user
- @user ||= User.find_by(id: params[:user_id])
- end
-
- def account
- @account ||= user.account
- end
-
- def oauth_authorize_endpoint(oauth_token)
- "#{twitter_api_base_url}/oauth/authorize?oauth_token=#{oauth_token}"
- end
-end
diff --git a/app/controllers/twitter/base_controller.rb b/app/controllers/twitter/base_controller.rb
index 353c1159c..9b4ba2a6a 100644
--- a/app/controllers/twitter/base_controller.rb
+++ b/app/controllers/twitter/base_controller.rb
@@ -1,24 +1,3 @@
class Twitter::BaseController < ApplicationController
- private
-
- def parsed_body
- @parsed_body ||= Rack::Utils.parse_nested_query(@response.raw_response.body)
- end
-
- def host
- ENV.fetch('FRONTEND_URL', '')
- end
-
- def twitter_client
- Twitty::Facade.new do |config|
- config.consumer_key = ENV.fetch('TWITTER_CONSUMER_KEY', nil)
- config.consumer_secret = ENV.fetch('TWITTER_CONSUMER_SECRET', nil)
- config.base_url = twitter_api_base_url
- config.environment = ENV.fetch('TWITTER_ENVIRONMENT', '')
- end
- end
-
- def twitter_api_base_url
- 'https://api.twitter.com'
- end
+ include TwitterConcern
end
diff --git a/app/controllers/twitter/callbacks_controller.rb b/app/controllers/twitter/callbacks_controller.rb
index b3cafbe12..d484b0871 100644
--- a/app/controllers/twitter/callbacks_controller.rb
+++ b/app/controllers/twitter/callbacks_controller.rb
@@ -1,4 +1,6 @@
class Twitter::CallbacksController < Twitter::BaseController
+ include TwitterConcern
+
def show
return redirect_to twitter_app_redirect_url if permitted_params[:denied]
diff --git a/app/finders/conversation_finder.rb b/app/finders/conversation_finder.rb
index 2aa7cb321..4336aff11 100644
--- a/app/finders/conversation_finder.rb
+++ b/app/finders/conversation_finder.rb
@@ -22,10 +22,12 @@ class ConversationFinder
def perform
set_inboxes
+ set_team
set_assignee_type
find_all_conversations
filter_by_status unless params[:q]
+ filter_by_team if @team
filter_by_labels if params[:labels]
filter_by_query if params[:q]
@@ -61,6 +63,10 @@ class ConversationFinder
@assignee_type = params[:assignee_type]
end
+ def set_team
+ @team = current_account.teams.find(params[:team_id]) if params[:team_id]
+ end
+
def find_all_conversations
@conversations = current_account.conversations.where(inbox_id: @inbox_ids)
end
@@ -87,6 +93,10 @@ class ConversationFinder
@conversations = @conversations.where(status: params[:status] || DEFAULT_STATUS)
end
+ def filter_by_team
+ @conversations = @conversations.where(team: @team)
+ end
+
def filter_by_labels
@conversations = @conversations.tagged_with(params[:labels], any: true)
end
diff --git a/app/helpers/file_type_helper.rb b/app/helpers/file_type_helper.rb
index 9f6939565..4936fa155 100644
--- a/app/helpers/file_type_helper.rb
+++ b/app/helpers/file_type_helper.rb
@@ -9,6 +9,8 @@ module FileTypeHelper
'image/bmp'
].include?(content_type)
+ return :audio if content_type.include?('audio/')
+
:file
end
end
diff --git a/app/javascript/dashboard/api/channel/twitterClient.js b/app/javascript/dashboard/api/channel/twitterClient.js
new file mode 100644
index 000000000..110cf6cde
--- /dev/null
+++ b/app/javascript/dashboard/api/channel/twitterClient.js
@@ -0,0 +1,14 @@
+/* global axios */
+import ApiClient from '../ApiClient';
+
+class TwitterClient extends ApiClient {
+ constructor() {
+ super('twitter', { accountScoped: true });
+ }
+
+ generateAuthorization() {
+ return axios.post(`${this.url}/authorization`);
+ }
+}
+
+export default new TwitterClient();
diff --git a/app/javascript/dashboard/api/inbox/conversation.js b/app/javascript/dashboard/api/inbox/conversation.js
index 9f87d2aa5..159e77f0e 100644
--- a/app/javascript/dashboard/api/inbox/conversation.js
+++ b/app/javascript/dashboard/api/inbox/conversation.js
@@ -6,10 +6,11 @@ class ConversationApi extends ApiClient {
super('conversations', { accountScoped: true });
}
- get({ inboxId, status, assigneeType, page, labels }) {
+ get({ inboxId, status, assigneeType, page, labels, teamId }) {
return axios.get(this.url, {
params: {
inbox_id: inboxId,
+ team_id: teamId,
status,
assignee_type: assigneeType,
page,
@@ -56,13 +57,14 @@ class ConversationApi extends ApiClient {
return axios.post(`${this.url}/${conversationId}/unmute`);
}
- meta({ inboxId, status, assigneeType, labels }) {
+ meta({ inboxId, status, assigneeType, labels, teamId }) {
return axios.get(`${this.url}/meta`, {
params: {
inbox_id: inboxId,
status,
assignee_type: assigneeType,
labels,
+ team_id: teamId,
},
});
}
diff --git a/app/javascript/dashboard/api/notifications.js b/app/javascript/dashboard/api/notifications.js
new file mode 100644
index 000000000..e13bc78a6
--- /dev/null
+++ b/app/javascript/dashboard/api/notifications.js
@@ -0,0 +1,33 @@
+/* global axios */
+import ApiClient from './ApiClient';
+
+class NotificationsAPI extends ApiClient {
+ constructor() {
+ super('notifications', { accountScoped: true });
+ }
+
+ get(page) {
+ return axios.get(`${this.url}?page=${page}`);
+ }
+
+ getNotifications(contactId) {
+ return axios.get(`${this.url}/${contactId}/notifications`);
+ }
+
+ getUnreadCount() {
+ return axios.get(`${this.url}/unread_count`);
+ }
+
+ read(primaryActorType, primaryActorId) {
+ return axios.post(`${this.url}/read_all`, {
+ primary_actor_type: primaryActorType,
+ primary_actor_id: primaryActorId,
+ });
+ }
+
+ readAll() {
+ return axios.post(`${this.url}/read_all`);
+ }
+}
+
+export default new NotificationsAPI();
diff --git a/app/javascript/dashboard/api/specs/channel/twitterClient.spec.js b/app/javascript/dashboard/api/specs/channel/twitterClient.spec.js
new file mode 100644
index 000000000..0480aeb82
--- /dev/null
+++ b/app/javascript/dashboard/api/specs/channel/twitterClient.spec.js
@@ -0,0 +1,9 @@
+import TwitterClient from '../../channel/twitterClient';
+import ApiClient from '../../ApiClient';
+
+describe('#TwitterClient', () => {
+ it('creates correct instance', () => {
+ expect(TwitterClient).toBeInstanceOf(ApiClient);
+ expect(TwitterClient).toHaveProperty('generateAuthorization');
+ });
+});
diff --git a/app/javascript/dashboard/api/specs/notifications.spec.js b/app/javascript/dashboard/api/specs/notifications.spec.js
new file mode 100644
index 000000000..14f472ce9
--- /dev/null
+++ b/app/javascript/dashboard/api/specs/notifications.spec.js
@@ -0,0 +1,13 @@
+import notifications from '../notifications';
+import ApiClient from '../ApiClient';
+
+describe('#NotificationAPI', () => {
+ it('creates correct instance', () => {
+ expect(notifications).toBeInstanceOf(ApiClient);
+ expect(notifications).toHaveProperty('get');
+ expect(notifications).toHaveProperty('getNotifications');
+ expect(notifications).toHaveProperty('getUnreadCount');
+ expect(notifications).toHaveProperty('read');
+ expect(notifications).toHaveProperty('readAll');
+ });
+});
diff --git a/app/javascript/dashboard/api/specs/teams.spec.js b/app/javascript/dashboard/api/specs/teams.spec.js
new file mode 100644
index 000000000..7622a5417
--- /dev/null
+++ b/app/javascript/dashboard/api/specs/teams.spec.js
@@ -0,0 +1,15 @@
+import teams from '../teams';
+import ApiClient from '../ApiClient';
+
+describe('#TeamsAPI', () => {
+ it('creates correct instance', () => {
+ expect(teams).toBeInstanceOf(ApiClient);
+ expect(teams).toHaveProperty('get');
+ expect(teams).toHaveProperty('show');
+ expect(teams).toHaveProperty('create');
+ expect(teams).toHaveProperty('update');
+ expect(teams).toHaveProperty('delete');
+ expect(teams).toHaveProperty('getAgents');
+ expect(teams).toHaveProperty('addAgents');
+ });
+});
diff --git a/app/javascript/dashboard/api/teams.js b/app/javascript/dashboard/api/teams.js
new file mode 100644
index 000000000..9c117c71b
--- /dev/null
+++ b/app/javascript/dashboard/api/teams.js
@@ -0,0 +1,20 @@
+/* global axios */
+import ApiClient from './ApiClient';
+
+export class TeamsAPI extends ApiClient {
+ constructor() {
+ super('teams', { accountScoped: true });
+ }
+
+ getAgents({ teamId }) {
+ return axios.get(`${this.url}/${teamId}/team_members`);
+ }
+
+ addAgents({ teamId, agentsList }) {
+ return axios.post(`${this.url}/${teamId}/team_members`, {
+ user_ids: agentsList,
+ });
+ }
+}
+
+export default new TeamsAPI();
diff --git a/app/javascript/dashboard/assets/scss/widgets/_reply-box.scss b/app/javascript/dashboard/assets/scss/widgets/_reply-box.scss
index 870a60c1b..d91e192d2 100644
--- a/app/javascript/dashboard/assets/scss/widgets/_reply-box.scss
+++ b/app/javascript/dashboard/assets/scss/widgets/_reply-box.scss
@@ -8,23 +8,6 @@
}
.reply-box__top {
- .canned {
- @include elegant-card;
- background: $color-white;
- border-bottom: $space-small solid $color-white;
- border-top: $space-small solid $color-white;
- left: 0;
-
- max-height: 14rem;
- overflow: auto;
- position: absolute;
- width: 24rem;
- z-index: 100;
-
- .active a {
- background: $color-woot;
- }
- }
.icon {
color: $medium-gray;
diff --git a/app/javascript/dashboard/assets/scss/widgets/_sidemenu.scss b/app/javascript/dashboard/assets/scss/widgets/_sidemenu.scss
index 3971ec6fd..344317735 100644
--- a/app/javascript/dashboard/assets/scss/widgets/_sidemenu.scss
+++ b/app/javascript/dashboard/assets/scss/widgets/_sidemenu.scss
@@ -49,8 +49,10 @@
margin-top: $space-micro;
.inbox-icon {
+ $icon-top-space: -1px;
display: inline-block;
margin-right: $space-micro;
+ margin-top: $icon-top-space;
min-width: $space-normal;
text-align: center;
}
@@ -70,9 +72,9 @@
.dropdown-pane {
@include elegant-card;
@include border-light;
+ bottom: 6rem;
display: block;
- left: 18%;
- top: -110px;
+ left: 5rem;
visibility: visible;
width: 80%;
z-index: 999;
@@ -118,42 +120,6 @@
line-height: $global-lineheight;
}
-.current-user {
- @include flex;
- align-items: center;
- cursor: pointer;
- flex-direction: row;
-
- .current-user--data {
- @include flex;
- flex-direction: column;
-
- .current-user--name {
- font-size: $font-size-small;
- font-weight: $font-weight-medium;
- line-height: 1;
- margin-bottom: $space-smaller;
- margin-left: $space-one;
- margin-top: $space-micro;
- }
-
- .current-user--role {
- color: $color-gray;
- font-size: $font-size-mini;
- margin-bottom: $zero;
- margin-left: $space-one;
- text-transform: capitalize;
- }
- }
-
- .current-user--options {
- font-size: $font-size-big;
- margin-bottom: auto;
- margin-left: auto;
- margin-top: auto;
- }
-}
-
.hamburger--menu {
cursor: pointer;
display: none;
diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue
index 06850ff38..8637c071a 100644
--- a/app/javascript/dashboard/components/ChatList.vue
+++ b/app/javascript/dashboard/components/ChatList.vue
@@ -80,6 +80,10 @@ export default {
type: String,
default: '',
},
+ activeTeam: {
+ type: Object,
+ default: () => {},
+ },
},
data() {
return {
@@ -128,12 +132,16 @@ export default {
status: this.activeStatus,
page: this.currentPage + 1,
labels: this.label ? [this.label] : undefined,
+ teamId: this.activeTeam.name ? this.activeTeam.id : undefined,
};
},
pageTitle() {
if (this.inbox.name) {
return this.inbox.name;
}
+ if (this.activeTeam.name) {
+ return this.activeTeam.name;
+ }
if (this.label) {
return `#${this.label}`;
}
@@ -162,6 +170,9 @@ export default {
},
},
watch: {
+ activeTeam() {
+ this.resetAndFetchData();
+ },
conversationInbox() {
this.resetAndFetchData();
},
diff --git a/app/javascript/dashboard/components/Code.vue b/app/javascript/dashboard/components/Code.vue
index 0be14acf3..b5edfa8d7 100644
--- a/app/javascript/dashboard/components/Code.vue
+++ b/app/javascript/dashboard/components/Code.vue
@@ -3,7 +3,7 @@
-
Labels
Labels help you to categorize conversations and prioritize them. You can assign label to a conversation from the sidepanel.
Labels are tied to the account and can be used to create custom workflows in your organization. You can assign custom color to a label, it makes it easier to identify the label. You will be able to display the label on the sidebar to filter the conversations easily.
\n برچسب ها\n
برچسب ها به شما در دسته بندی مکالمات و اولویت بندی آنها کمک می کند. می توانید برچسب را به یک مکالمه از نوار کناری اختصاص دهید.\n
\n
\n برچسب ها به حساب گره خورده اند و می توانند برای ایجاد گردش کار سفارشی در سازمان شما استفاده شوند. می توانید رنگ سفارشی را به برچسب اختصاص دهید ، شناسایی برچسب آسان تر می شود. شما می توانید برچسب را در نوار کناری نمایش دهید تا مکالمه ها به راحتی فیلتر شود.\n
Agents
An Agent is a member of your Customer Support team.
Agents will be able to view and reply to messages from your users. The list shows all agents currently in your account.
Click on Add Agent to add a new agent. Agent you add will receive an email with a confirmation link to activate their account, after which they can access Chatwoot and respond to messages.
Access to Chatwoot's features are based on following roles.
Agent - Agents with this role can only access inboxes, reports and conversations. They can assign conversations to other agents or themselves and resolve conversations.
Administrator - Administrator will have access to all Chatwoot features enabled for your account, including settings, along with all of a normal agents' privileges.
", + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, + "LIST": { + "404": "There are no agents associated to this account", + "TITLE": "Manage agents in your team", + "DESC": "You can add/remove agents to/in your team.", + "NAME": "Name", + "EMAIL": "EMAIL", + "STATUS": "Status", + "ACTIONS": "Actions", + "VERIFIED": "Verified", + "VERIFICATION_PENDING": "Verification Pending" + }, + "ADD": { + "TITLE": "Add agent to your team", + "DESC": "You can add people who will be able to handle support for your inboxes.", + "CANCEL_BUTTON_TEXT": "Cancel", + "FORM": { + "NAME": { + "LABEL": "Agent Name", + "PLACEHOLDER": "Please enter a name of the agent" + }, + "AGENT_TYPE": { + "LABEL": "Role", + "PLACEHOLDER": "Please select a role", + "ERROR": "Role is required" + }, + "EMAIL": { + "LABEL": "Email Address", + "PLACEHOLDER": "Please enter an email address of the agent" + }, + "SUBMIT": "Add Agent" + }, + "API": { + "SUCCESS_MESSAGE": "Agent added successfully", + "EXIST_MESSAGE": "Agent email already in use, Please try another email address", + "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + } + }, + "DELETE": { + "BUTTON_TEXT": "Delete", + "API": { + "SUCCESS_MESSAGE": "Agent deleted successfully", + "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + }, + "CONFIRM": { + "TITLE": "Confirm Deletion", + "MESSAGE": "Are you sure to delete ", + "YES": "Yes, Delete ", + "NO": "No, Keep " + } + }, + "EDIT": { + "TITLE": "Edit agent", + "FORM": { + "NAME": { + "LABEL": "Agent Name", + "PLACEHOLDER": "Please enter a name of the agent" + }, + "AGENT_TYPE": { + "LABEL": "Role", + "PLACEHOLDER": "Please select a role", + "ERROR": "Role is required" + }, + "EMAIL": { + "LABEL": "Email Address", + "PLACEHOLDER": "Please enter an email address of the agent" + }, + "SUBMIT": "Edit Agent" + }, + "BUTTON_TEXT": "Edit", + "CANCEL_BUTTON_TEXT": "Cancel", + "API": { + "SUCCESS_MESSAGE": "Agent updated successfully", + "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + }, + "PASSWORD_RESET": { + "ADMIN_RESET_BUTTON": "Reset Password", + "ADMIN_SUCCESS_MESSAGE": "An email with reset password instructions has been sent to the agent", + "SUCCESS_MESSAGE": "Agent password reset successfully", + "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + } + }, + "SEARCH": { + "NO_RESULTS": "No agents found." + } + } +} diff --git a/app/javascript/dashboard/i18n/locale/ne/cannedMgmt.json b/app/javascript/dashboard/i18n/locale/ne/cannedMgmt.json new file mode 100644 index 000000000..bcab1dc6a --- /dev/null +++ b/app/javascript/dashboard/i18n/locale/ne/cannedMgmt.json @@ -0,0 +1,76 @@ +{ + "CANNED_MGMT": { + "HEADER": "Canned Responses", + "HEADER_BTN_TXT": "Add Canned Response", + "LOADING": "Fetching Canned Responses", + "SEARCH_404": "There are no items matching this query", + "SIDEBAR_TXT": "Canned Responses
Canned Responses are saved reply templates which can be used to quickly send out a reply to a conversation .
For creating a Canned Response, just click on the Add Canned Response. You can also edit or delete an existing Canned Response by clicking on the Edit or Delete button
Canned responses are used with the help of Short Codes. Agents can access canned responses while on a chat by typing '/' followed by the short code.
", + "LIST": { + "404": "There are no canned responses available in this account.", + "TITLE": "Manage canned responses", + "DESC": "Canned Responses are predefined reply templates which can be used to quickly send out replies to tickets.", + "TABLE_HEADER": [ + "Short Code", + "Content", + "Actions" + ] + }, + "ADD": { + "TITLE": "Add Canned Response", + "DESC": "Canned Responses are saved reply templates which can be used to quickly send out reply to conversation .", + "CANCEL_BUTTON_TEXT": "Cancel", + "FORM": { + "SHORT_CODE": { + "LABEL": "Short Code", + "PLACEHOLDER": "Please enter a shortcode", + "ERROR": "Short Code is required" + }, + "CONTENT": { + "LABEL": "Content", + "PLACEHOLDER": "Please enter a content", + "ERROR": "Content is required" + }, + "SUBMIT": "Submit" + }, + "API": { + "SUCCESS_MESSAGE": "Canned Response added successfully", + "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + } + }, + "EDIT": { + "TITLE": "Edit Canned Response", + "CANCEL_BUTTON_TEXT": "Cancel", + "FORM": { + "SHORT_CODE": { + "LABEL": "Short Code", + "PLACEHOLDER": "Please enter a shortcode", + "ERROR": "Short Code is required" + }, + "CONTENT": { + "LABEL": "Content", + "PLACEHOLDER": "Please enter a content", + "ERROR": "Content is required" + }, + "SUBMIT": "Submit" + }, + "BUTTON_TEXT": "Edit", + "API": { + "SUCCESS_MESSAGE": "Canned Response updated successfully", + "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + } + }, + "DELETE": { + "BUTTON_TEXT": "Delete", + "API": { + "SUCCESS_MESSAGE": "Canned response deleted successfully", + "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + }, + "CONFIRM": { + "TITLE": "Confirm Deletion", + "MESSAGE": "Are you sure to delete ", + "YES": "Yes, Delete ", + "NO": "No, Keep " + } + } + } +} diff --git a/app/javascript/dashboard/i18n/locale/ne/chatlist.json b/app/javascript/dashboard/i18n/locale/ne/chatlist.json new file mode 100644 index 000000000..228554789 --- /dev/null +++ b/app/javascript/dashboard/i18n/locale/ne/chatlist.json @@ -0,0 +1,85 @@ +{ + "CHAT_LIST": { + "LOADING": "Fetching conversations", + "LOAD_MORE_CONVERSATIONS": "Load more conversations", + "EOF": "All conversations loaded 🎉", + "LIST": { + "404": "There are no active conversations in this group." + }, + "TAB_HEADING": "Conversations", + "SEARCH": { + "INPUT": "Search for People, Chats, Saved Replies .." + }, + "STATUS_TABS": [ + { + "NAME": "Open", + "KEY": "openCount" + }, + { + "NAME": "Resolved", + "KEY": "allConvCount" + } + ], + "ASSIGNEE_TYPE_TABS": [ + { + "NAME": "Mine", + "KEY": "me", + "COUNT_KEY": "mineCount" + }, + { + "NAME": "Unassigned", + "KEY": "unassigned", + "COUNT_KEY": "unAssignedCount" + }, + { + "NAME": "All", + "KEY": "all", + "COUNT_KEY": "allCount" + } + ], + "CHAT_STATUS_ITEMS": [ + { + "TEXT": "Open", + "VALUE": "open" + }, + { + "TEXT": "Resolved", + "VALUE": "resolved" + }, + { + "TEXT": "Bot", + "VALUE": "bot" + } + ], + "ATTACHMENTS": { + "image": { + "ICON": "ion-image", + "CONTENT": "Picture message" + }, + "audio": { + "ICON": "ion-volume-high", + "CONTENT": "Audio message" + }, + "video": { + "ICON": "ion-ios-videocam", + "CONTENT": "Video message" + }, + "file": { + "ICON": "ion-document", + "CONTENT": "File Attachment" + }, + "location": { + "ICON": "ion-ios-location", + "CONTENT": "Location" + }, + "fallback": { + "ICON": "ion-link", + "CONTENT": "has shared a url" + } + }, + "RECEIVED_VIA_EMAIL": "Received via email", + "VIEW_TWEET_IN_TWITTER": "View tweet in Twitter", + "REPLY_TO_TWEET": "Reply to this tweet", + "NO_MESSAGES": "No Messages" + } +} diff --git a/app/javascript/dashboard/i18n/locale/ne/contact.json b/app/javascript/dashboard/i18n/locale/ne/contact.json new file mode 100644 index 000000000..da85ffab8 --- /dev/null +++ b/app/javascript/dashboard/i18n/locale/ne/contact.json @@ -0,0 +1,115 @@ +{ + "CONTACT_PANEL": { + "NOT_AVAILABLE": "Not Available", + "EMAIL_ADDRESS": "Email Address", + "PHONE_NUMBER": "Phone number", + "COPY_SUCCESSFUL": "Copied to clipboard successfully", + "COMPANY": "Company", + "LOCATION": "Location", + "CONVERSATION_TITLE": "Conversation Details", + "BROWSER": "Browser", + "OS": "Operating System", + "INITIATED_FROM": "Initiated from", + "INITIATED_AT": "Initiated at", + "IP_ADDRESS": "IP Address", + "CONVERSATIONS": { + "NO_RECORDS_FOUND": "There are no previous conversations associated to this contact.", + "TITLE": "Previous Conversations" + }, + "CUSTOM_ATTRIBUTES": { + "TITLE": "Custom Attributes" + }, + "LABELS": { + "TITLE": "Conversation Labels", + "MODAL": { + "TITLE": "Labels for", + "ACTIVE_LABELS": "Labels added to the conversation", + "INACTIVE_LABELS": "Labels available in the account", + "REMOVE": "Click on X icon to remove the label", + "ADD": "Click on + icon to add the label", + "UPDATE_BUTTON": "Update labels", + "UPDATE_ERROR": "Couldn't update labels, try again." + }, + "NO_LABELS_TO_ADD": "There are no more labels defined in the account.", + "NO_AVAILABLE_LABELS": "There are no labels added to this conversation." + }, + "MUTE_CONTACT": "Mute Conversation", + "UNMUTE_CONTACT": "Unmute Conversation", + "MUTED_SUCCESS": "This conversation is muted for 6 hours", + "UNMUTED_SUCCESS": "This conversation is unmuted", + "SEND_TRANSCRIPT": "Send Transcript", + "EDIT_LABEL": "Edit" + }, + "EDIT_CONTACT": { + "BUTTON_LABEL": "Edit Contact", + "TITLE": "Edit contact", + "DESC": "Edit contact details", + "FORM": { + "SUBMIT": "Submit", + "CANCEL": "Cancel", + "AVATAR": { + "LABEL": "Contact Avatar" + }, + "NAME": { + "PLACEHOLDER": "Enter the full name of the contact", + "LABEL": "Full Name" + }, + "BIO": { + "PLACEHOLDER": "Enter the bio of the contact", + "LABEL": "Bio" + }, + "EMAIL_ADDRESS": { + "PLACEHOLDER": "Enter the email address of the contact", + "LABEL": "Email Address" + }, + "PHONE_NUMBER": { + "PLACEHOLDER": "Enter the phone number of the contact", + "LABEL": "Phone Number" + }, + "LOCATION": { + "PLACEHOLDER": "Enter the location of the contact", + "LABEL": "Location" + }, + "COMPANY_NAME": { + "PLACEHOLDER": "Enter the company name", + "LABEL": "Company Name" + }, + "SOCIAL_PROFILES": { + "FACEBOOK": { + "PLACEHOLDER": "Enter the Facebook username", + "LABEL": "Facebook" + }, + "TWITTER": { + "PLACEHOLDER": "Enter the Twitter username", + "LABEL": "Twitter" + }, + "LINKEDIN": { + "PLACEHOLDER": "Enter the LinkedIn username", + "LABEL": "LinkedIn" + }, + "GITHUB": { + "PLACEHOLDER": "Enter the Github username", + "LABEL": "Github" + } + } + }, + "SUCCESS_MESSAGE": "Updated contact successfully", + "CONTACT_ALREADY_EXIST": "This email address is in use for another contact.", + "ERROR_MESSAGE": "There was an error updating the contact, please try again" + }, + "CONTACTS_PAGE": { + "HEADER": "Contacts", + "SEARCH_BUTTON": "Search", + "SEARCH_INPUT_PLACEHOLDER": "Search for contacts", + "LIST": { + "LOADING_MESSAGE": "Loading contacts...", + "404": "No contacts matches your search 🔍", + "TABLE_HEADER": [ + "Name", + "Phone Number", + "Conversations", + "Last Contacted" + ] + } + } +} diff --git a/app/javascript/dashboard/i18n/locale/ne/conversation.json b/app/javascript/dashboard/i18n/locale/ne/conversation.json new file mode 100644 index 000000000..967378552 --- /dev/null +++ b/app/javascript/dashboard/i18n/locale/ne/conversation.json @@ -0,0 +1,78 @@ +{ + "CONVERSATION": { + "404": "Please select a conversation from left pane", + "NO_MESSAGE_1": "Uh oh! Looks like there are no messages from customers in your inbox.", + "NO_MESSAGE_2": " to send a message to your page!", + "NO_INBOX_1": "Hola! Looks like you haven't added any inboxes yet.", + "NO_INBOX_2": " to get started", + "NO_INBOX_AGENT": "Uh Oh! Looks like you are not part of any inbox. Please contact your administrator", + "SEARCH_MESSAGES": "Search for messages in conversations", + "SEARCH": { + "TITLE": "Search messages", + "LOADING_MESSAGE": "Crunching data...", + "PLACEHOLDER": "Type any text to search messages", + "NO_MATCHING_RESULTS": "There are no messages matching the search parameters." + }, + "UNREAD_MESSAGES": "Unread Messages", + "UNREAD_MESSAGE": "Unread Message", + "CLICK_HERE": "Click here", + "LOADING_INBOXES": "Loading inboxes", + "LOADING_CONVERSATIONS": "Loading Conversations", + "CANNOT_REPLY": "You cannot reply due to", + "24_HOURS_WINDOW": "24 hour message window restriction", + "LAST_INCOMING_TWEET": "You are replying to the last incoming tweet", + "REPLYING_TO": "You are replying to:", + "REMOVE_SELECTION": "Remove Selection", + "DOWNLOAD": "Download", + "UPLOADING_ATTACHMENTS": "Uploading attachments...", + "NO_RESPONSE": "No response", + "HEADER": { + "RESOLVE_ACTION": "Resolve", + "REOPEN_ACTION": "Reopen", + "OPEN": "More", + "CLOSE": "Close", + "DETAILS": "details" + }, + "FOOTER": { + "MSG_INPUT": "Shift + enter for new line. Start with '/' to select a Canned Response.", + "PRIVATE_MSG_INPUT": "Shift + enter for new line. This will be visible only to Agents" + }, + "REPLYBOX": { + "REPLY": "Reply", + "PRIVATE_NOTE": "Private Note", + "SEND": "Send", + "CREATE": "Add Note", + "TWEET": "Tweet", + "TIP_FORMAT_ICON": "Show rich text editor", + "TIP_EMOJI_ICON": "Show emoji selector", + "TIP_ATTACH_ICON": "Attach files", + "ENTER_TO_SEND": "Enter to send" + }, + "VISIBLE_TO_AGENTS": "Private Note: Only visible to you and your team", + "CHANGE_STATUS": "Conversation status changed", + "CHANGE_AGENT": "Conversation Assignee changed", + "SENT_BY": "Sent by:", + "ASSIGNMENT": { + "SELECT_AGENT": "Select Agent", + "REMOVE": "Remove", + "ASSIGN": "Assign" + } + }, + "EMAIL_TRANSCRIPT": { + "TITLE": "Send conversation transcript", + "DESC": "Send a copy of the conversation transcript to the specified email address", + "SUBMIT": "Submit", + "CANCEL": "Cancel", + "SEND_EMAIL_SUCCESS": "The chat transcript was sent successfully", + "SEND_EMAIL_ERROR": "There was an error, please try again", + "FORM": { + "SEND_TO_CONTACT": "Send the transcript to the customer", + "SEND_TO_AGENT": "Send the transcript to the assigned agent", + "SEND_TO_OTHER_EMAIL_ADDRESS": "Send the transcript to another email address", + "EMAIL": { + "PLACEHOLDER": "Enter an email address", + "ERROR": "Please enter a valid email address" + } + } + } +} diff --git a/app/javascript/dashboard/i18n/locale/ne/generalSettings.json b/app/javascript/dashboard/i18n/locale/ne/generalSettings.json new file mode 100644 index 000000000..d5e8bd50b --- /dev/null +++ b/app/javascript/dashboard/i18n/locale/ne/generalSettings.json @@ -0,0 +1,75 @@ +{ + "GENERAL_SETTINGS": { + "TITLE": "Account settings", + "SUBMIT": "Update settings", + "BACK": "Back", + "UPDATE": { + "ERROR": "Could not update settings, try again!", + "SUCCESS": "Successfully updated account settings" + }, + "FORM": { + "ERROR": "Please fix form errors", + "GENERAL_SECTION": { + "TITLE": "General settings", + "NOTE": "" + }, + "NAME": { + "LABEL": "Account name", + "PLACEHOLDER": "Your account name", + "ERROR": "Please enter a valid account name" + }, + "LANGUAGE": { + "LABEL": "Site language (Beta)", + "PLACEHOLDER": "Your account name", + "ERROR": "" + }, + "DOMAIN": { + "LABEL": "Incoming Email Domain", + "PLACEHOLDER": "The domain where you will receive the emails", + "ERROR": "" + }, + "SUPPORT_EMAIL": { + "LABEL": "Support Email", + "PLACEHOLDER": "Your company's support email", + "ERROR": "" + }, + "AUTO_RESOLVE_DURATION": { + "LABEL": "Number of days after a ticket should auto resolve if there is no activity", + "PLACEHOLDER": "30", + "ERROR": "Please enter a valid auto resolve duration (minimum 1 day)" + }, + "FEATURES": { + "INBOUND_EMAIL_ENABLED": "Conversation continuity with emails is enabled for your account.", + "CUSTOM_EMAIL_DOMAIN_ENABLED": "You can receive emails in your custom domain now." + } + }, + "UPDATE_CHATWOOT": "An update %{latestChatwootVersion} for Chatwoot is available. Please update your instance." + }, + "FORMS": { + "MULTISELECT": { + "ENTER_TO_SELECT": "Press enter to select", + "ENTER_TO_REMOVE": "Press enter to remove", + "SELECT_ONE": "Select one" + } + }, + "NOTIFICATIONS_PAGE": { + "HEADER": "Notifications", + "MARK_ALL_DONE": "Mark All Done", + "LIST": { + "LOADING_MESSAGE": "Loading notifications...", + "404": "No Notifications", + "TABLE_HEADER": [ + "Name", + "Phone Number", + "Conversations", + "Last Contacted" + ] + }, + "TYPE_LABEL": { + "conversation_creation": "New conversation", + "conversation_assignment": "Conversation Assigned", + "assigned_conversation_new_message": "New Message", + "conversation_mention": "Mention" + } + } +} diff --git a/app/javascript/dashboard/i18n/locale/ne/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/ne/inboxMgmt.json new file mode 100644 index 000000000..8c3f5624b --- /dev/null +++ b/app/javascript/dashboard/i18n/locale/ne/inboxMgmt.json @@ -0,0 +1,255 @@ +{ + "INBOX_MGMT": { + "HEADER": "Inboxes", + "SIDEBAR_TXT": "Inbox
When you connect a website or a facebook Page to Chatwoot, it is called an Inbox. You can have unlimited inboxes in your Chatwoot account.
Click on Add Inbox to connect a website or a Facebook Page.
In the Dashboard, you can see all the conversations from all your inboxes in a single place and respond to them under the `Conversations` tab.
You can also see conversations specific to an inbox by clicking on the inbox name on the left pane of the dashboard.
", + "LIST": { + "404": "There are no inboxes attached to this account." + }, + "CREATE_FLOW": [ + { + "title": "Choose Channel", + "route": "settings_inbox_new", + "body": "Choose the provider you want to integrate with Chatwoot." + }, + { + "title": "Create Inbox", + "route": "settings_inboxes_page_channel", + "body": "Authenticate your account and create an inbox." + }, + { + "title": "Add Agents", + "route": "settings_inboxes_add_agents", + "body": "Add agents to the created inbox." + }, + { + "title": "Voila!", + "route": "settings_inbox_finish", + "body": "You are all set to go!" + } + ], + "ADD": { + "FB": { + "HELP": "PS: By signing in, we only get access to your Page's messages. Your private messages can never be accessed by Chatwoot.", + "CHOOSE_PAGE": "Choose Page", + "CHOOSE_PLACEHOLDER": "Select a page from the list", + "INBOX_NAME": "Inbox Name", + "ADD_NAME": "Add a name for your inbox", + "PICK_NAME": "Pick A Name Your Inbox", + "PICK_A_VALUE": "Pick a value" + }, + "TWITTER": { + "HELP": "To add your Twitter profile as a channel, you need to authenticate your Twitter Profile by clicking on 'Sign in with Twitter' " + }, + "WEBSITE_CHANNEL": { + "TITLE": "Website channel", + "DESC": "Create a channel for your website and start supporting your customers via our website widget.", + "LOADING_MESSAGE": "Creating Website Support Channel", + "CHANNEL_AVATAR": { + "LABEL": "Channel Avatar" + }, + "CHANNEL_NAME": { + "LABEL": "Website Name", + "PLACEHOLDER": "Enter your website name (eg: Acme Inc)" + }, + "CHANNEL_DOMAIN": { + "LABEL": "Website Domain", + "PLACEHOLDER": "Enter your website domain (eg: acme.com)" + }, + "CHANNEL_WELCOME_TITLE": { + "LABEL": "Welcome Heading", + "PLACEHOLDER": "Hi there !" + }, + "CHANNEL_WELCOME_TAGLINE": { + "LABEL": "Welcome Tagline", + "PLACEHOLDER": "We make it simple to connect with us. Ask us anything, or share your feedback." + }, + "CHANNEL_GREETING_MESSAGE": { + "LABEL": "Channel greeting message", + "PLACEHOLDER": "Acme Inc typically replies in a few hours." + }, + "CHANNEL_GREETING_TOGGLE": { + "LABEL": "Enable channel greeting", + "HELP_TEXT": "Send a greeting message to the user when he starts the conversation.", + "ENABLED": "Enabled", + "DISABLED": "Disabled" + }, + "REPLY_TIME": { + "TITLE": "Set Reply time", + "IN_A_FEW_MINUTES": "In a few minutes", + "IN_A_FEW_HOURS": "In a few hours", + "IN_A_DAY": "In a day", + "HELP_TEXT": "This reply time will be displayed on the live chat widget" + }, + "WIDGET_COLOR": { + "LABEL": "Widget Color", + "PLACEHOLDER": "Update the widget color used in widget" + }, + "SUBMIT_BUTTON": "Create inbox" + }, + "TWILIO": { + "TITLE": "Twilio SMS/Whatsapp Channel", + "DESC": "Integrate Twilio and start supporting your customers via SMS or Whatsapp.", + "ACCOUNT_SID": { + "LABEL": "Account SID", + "PLACEHOLDER": "Please enter your Twilio Account SID", + "ERROR": "This field is required" + }, + "CHANNEL_TYPE": { + "LABEL": "Channel Type", + "ERROR": "Please select your Channel Type" + }, + "AUTH_TOKEN": { + "LABEL": "Auth Token", + "PLACEHOLDER": "Please enter your Twilio Auth Token", + "ERROR": "This field is required" + }, + "CHANNEL_NAME": { + "LABEL": "Channel Name", + "PLACEHOLDER": "Please enter a channel name", + "ERROR": "This field is required" + }, + "PHONE_NUMBER": { + "LABEL": "Phone number", + "PLACEHOLDER": "Please enter the phone number from which message will be sent.", + "ERROR": "Please enter a valid value. Phone number should start with `+` sign." + }, + "API_CALLBACK": { + "TITLE": "Callback URL", + "SUBTITLE": "You have to configure the message callback URL in Twilio with the URL mentioned here." + }, + "SUBMIT_BUTTON": "Create Twilio Channel", + "API": { + "ERROR_MESSAGE": "We were not able to authenticate Twilio credentials, please try again" + } + }, + "API_CHANNEL": { + "TITLE": "API Channel", + "DESC": "Integrate with API channel and start supporting your customers.", + "CHANNEL_NAME": { + "LABEL": "Channel Name", + "PLACEHOLDER": "Please enter a channel name", + "ERROR": "This field is required" + }, + "WEBHOOK_URL": { + "LABEL": "Webhook URL", + "SUBTITLE": "Configure the URL where you want to recieve callbacks on events.", + "PLACEHOLDER": "Webhook URL" + }, + "SUBMIT_BUTTON": "Create API Channel", + "API": { + "ERROR_MESSAGE": "We were not able to save the api channel" + } + }, + "EMAIL_CHANNEL": { + "TITLE": "Email Channel", + "DESC": "Integrate you email inbox.", + "CHANNEL_NAME": { + "LABEL": "Channel Name", + "PLACEHOLDER": "Please enter a channel name", + "ERROR": "This field is required" + }, + "EMAIL": { + "LABEL": "Email", + "SUBTITLE": "Email where your customers sends you support tickets", + "PLACEHOLDER": "Email" + }, + "SUBMIT_BUTTON": "Create Email Channel", + "API": { + "ERROR_MESSAGE": "We were not able to save the email channel" + }, + "FINISH_MESSAGE": "Start forwarding your emails to the following email address." + }, + "AUTH": { + "TITLE": "Channels", + "DESC": "Currently, we support Website live chat widgets, Facebook Pages, Twitter profiles and Whatsapp as platforms. We have more platforms like Email, Telegram and Line in the works, which will be out soon." + }, + "AGENTS": { + "TITLE": "Agents", + "DESC": "Here you can add agents to manage your newly created inbox. Only these selected agents will have access to your inbox. Agents which are not part of this inbox will not be able to see or respond to messages in this inbox when they login.Webhooks
Webhooks are HTTP callbacks which can be defined for every account. They are triggered by events like message creation in Chatwoot. You can create more than one webhook for this account.
For creating a webhook, click on the Add new webhook button. You can also remove any existing webhook by clicking on the Delete button.
Labels
Labels help you to categorize conversations and prioritize them. You can assign label to a conversation from the sidepanel.
Labels are tied to the account and can be used to create custom workflows in your organization. You can assign custom color to a label, it makes it easier to identify the label. You will be able to display the label on the sidebar to filter the conversations easily.
Agenter
En agent er en medlem i ditt kundeserivce-team.
Agenter vil ha mulighet til å se på og svare på henvendelser fra dine brukere. Listen viser alle agenter i din konto
Klikk på Legg til agent for å legge til agent. Agenten du legger til vil motta en e-post med en link for å bekrefte kontoen deres. Etter dette får de tilgang til Chatwoot og kan begynne å svare på henvendelser.
Tilgang til funksjonene i Chatwoot er basert på følgende roller.
Agent - Agenter med denne rollen har bare tilgang til innbokser, rapporter og samtaler. De kan tildele samtaler til seg selv og andre agenter, og markere samtaler som løst.
Administrator - En administrator har tilgang til alle funksjoner og innstillinger i Chatwoot, i tillegg til det en agent vil ha tilgang til.
", + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, + "LIST": { + "404": "Det er ingen agenter tilknyttet denne kontoen", + "TITLE": "Administrer agentene i ditt team", + "DESC": "Du kan legge til eller fjerne agenter i ditt team.", + "NAME": "Navn", + "EMAIL": "E-POST", + "STATUS": "Satus", + "ACTIONS": "Handlinger", + "VERIFIED": "Verifisert", + "VERIFICATION_PENDING": "Verifisering venter" + }, + "ADD": { + "TITLE": "Legg til agent i teamet ditt", + "DESC": "Du kan legge til personer som vil kunne håndtere støtte for dine innbokser.", + "CANCEL_BUTTON_TEXT": "Avbryt", + "FORM": { + "NAME": { + "LABEL": "Agent navn", + "PLACEHOLDER": "Skriv inn navnet til agenten" + }, + "AGENT_TYPE": { + "LABEL": "Rolle", + "PLACEHOLDER": "Velg en rolle", + "ERROR": "En rolle er påkrevd" + }, + "EMAIL": { + "LABEL": "E-postadresse", + "PLACEHOLDER": "Skriv inn en agent sin e-postadresse" + }, + "SUBMIT": "Legg til agent" + }, + "API": { + "SUCCESS_MESSAGE": "Agent ble lagt til", + "EXIST_MESSAGE": "E-postadressen til agenten er allerede i bruk, prøv en annen e-postadresse", + "ERROR_MESSAGE": "Kunne ikke koble til Woot Server, vennligst prøv igjen senere" + } + }, + "DELETE": { + "BUTTON_TEXT": "Slett", + "API": { + "SUCCESS_MESSAGE": "Agenten ble slettet", + "ERROR_MESSAGE": "Kunne ikke koble til Woot Server, vennligst prøv igjen senere" + }, + "CONFIRM": { + "TITLE": "Bekreft sletting", + "MESSAGE": "Er du sikker på at du vil slette ", + "YES": "Ja, slett ", + "NO": "Nei, behold " + } + }, + "EDIT": { + "TITLE": "Rediger agent", + "FORM": { + "NAME": { + "LABEL": "Agent navn", + "PLACEHOLDER": "Skriv inn navnet til agenten" + }, + "AGENT_TYPE": { + "LABEL": "Rolle", + "PLACEHOLDER": "Velg en rolle", + "ERROR": "En rolle er påkrevd" + }, + "EMAIL": { + "LABEL": "E-postadresse", + "PLACEHOLDER": "Skriv inn en agent sin e-postadresse" + }, + "SUBMIT": "Rediger agent" + }, + "BUTTON_TEXT": "Rediger", + "CANCEL_BUTTON_TEXT": "Avbryt", + "API": { + "SUCCESS_MESSAGE": "Agenten ble oppdatert", + "ERROR_MESSAGE": "Kunne ikke koble til Woot Server, vennligst prøv igjen senere" + }, + "PASSWORD_RESET": { + "ADMIN_RESET_BUTTON": "Tilbakestill passord", + "ADMIN_SUCCESS_MESSAGE": "En e-post med instruksjoner for å tilbakestille passordet er sendt til agenten", + "SUCCESS_MESSAGE": "Passordet til agenten ble tilbakestilt", + "ERROR_MESSAGE": "Kunne ikke koble til Woot Server, vennligst prøv igjen senere" + } + }, + "SEARCH": { + "NO_RESULTS": "Ingen agenter funnet." + } + } +} diff --git a/app/javascript/dashboard/i18n/locale/no/cannedMgmt.json b/app/javascript/dashboard/i18n/locale/no/cannedMgmt.json new file mode 100644 index 000000000..e0b6e9218 --- /dev/null +++ b/app/javascript/dashboard/i18n/locale/no/cannedMgmt.json @@ -0,0 +1,76 @@ +{ + "CANNED_MGMT": { + "HEADER": "Forhåndslagde svar", + "HEADER_BTN_TXT": "Legg til svar", + "LOADING": "Henter forhåndslagde svar", + "SEARCH_404": "Det finnes ingen elementer som samsvarer med denne spørringen", + "SIDEBAR_TXT": "Forhåndslagde svar
Forhåndslagde svar er forhåndsdefinerte maler som kan bli brukt til å sende ut et raskt svar på en samtale.
For å lage et forhåndslagd svar, klikk på Legg til svar. Du kan også redigere eller slette et eksisterende forhåndslagd svar ved å klikke på rediger eller slett
Forhåndslagde svar kan brukes ved hjelp av Korte koder. Agenter kan få tilgang til svar mens de er i en samtale ved å skrive '/' etterfulgt av den korte koden.
", + "LIST": { + "404": "Det er ingen forhåndslagde svar tilgjengelig i denne kontoen.", + "TITLE": "Administrer forhåndslagde svar", + "DESC": "Forhåndslagde svar er predefinerte maler som kan brukes for å raskt sende ut svar til henvendelser.", + "TABLE_HEADER": [ + "Kort kode", + "Innhold", + "Handlinger" + ] + }, + "ADD": { + "TITLE": "Legg til svar", + "DESC": "Forhåndslagde svar er predefinerte maler som kan brukes for å raskt sende ut svar til henvendelser.", + "CANCEL_BUTTON_TEXT": "Avbryt", + "FORM": { + "SHORT_CODE": { + "LABEL": "Kort kode", + "PLACEHOLDER": "Angi en kort kode", + "ERROR": "Kort kode kreves" + }, + "CONTENT": { + "LABEL": "Innhold", + "PLACEHOLDER": "Angi en kort kode", + "ERROR": "Innhold er påkrevd" + }, + "SUBMIT": "Send" + }, + "API": { + "SUCCESS_MESSAGE": "Forhåndslagd svar ble lagt til", + "ERROR_MESSAGE": "Kunne ikke koble til Woot Server, vennligst prøv igjen senere" + } + }, + "EDIT": { + "TITLE": "Rediger forhåndslagd svar", + "CANCEL_BUTTON_TEXT": "Avbryt", + "FORM": { + "SHORT_CODE": { + "LABEL": "Kort kode", + "PLACEHOLDER": "Angi en kort kode", + "ERROR": "Kort kode kreves" + }, + "CONTENT": { + "LABEL": "Innhold", + "PLACEHOLDER": "Angi en kort kode", + "ERROR": "Innhold er påkrevd" + }, + "SUBMIT": "Send" + }, + "BUTTON_TEXT": "Rediger", + "API": { + "SUCCESS_MESSAGE": "Forhåndslagd svar ble oppdatert", + "ERROR_MESSAGE": "Kunne ikke koble til Woot Server, vennligst prøv igjen senere" + } + }, + "DELETE": { + "BUTTON_TEXT": "Slett", + "API": { + "SUCCESS_MESSAGE": "Forhåndslagd svar ble slettet", + "ERROR_MESSAGE": "Kunne ikke koble til Woot Server, vennligst prøv igjen senere" + }, + "CONFIRM": { + "TITLE": "Bekreft sletting", + "MESSAGE": "Er du sikker på at du vil slette ", + "YES": "Ja, slett ", + "NO": "Nei, behold " + } + } + } +} diff --git a/app/javascript/dashboard/i18n/locale/no/chatlist.json b/app/javascript/dashboard/i18n/locale/no/chatlist.json new file mode 100644 index 000000000..1d021333f --- /dev/null +++ b/app/javascript/dashboard/i18n/locale/no/chatlist.json @@ -0,0 +1,85 @@ +{ + "CHAT_LIST": { + "LOADING": "Henter samtaler", + "LOAD_MORE_CONVERSATIONS": "Last inn flere samtaler", + "EOF": "Alle samtaler lastet 🎉", + "LIST": { + "404": "Det er ingen aktive samtaler i denne gruppen." + }, + "TAB_HEADING": "Samtaler", + "SEARCH": { + "INPUT": "Søk etter personer, samtaler, lagrede svar .." + }, + "STATUS_TABS": [ + { + "NAME": "Åpne", + "KEY": "openCount" + }, + { + "NAME": "Løst", + "KEY": "allConvCount" + } + ], + "ASSIGNEE_TYPE_TABS": [ + { + "NAME": "Mine", + "KEY": "me", + "COUNT_KEY": "mineCount" + }, + { + "NAME": "Ikke tildelt", + "KEY": "unassigned", + "COUNT_KEY": "unAssignedCount" + }, + { + "NAME": "Alle", + "KEY": "all", + "COUNT_KEY": "allCount" + } + ], + "CHAT_STATUS_ITEMS": [ + { + "TEXT": "Åpne", + "VALUE": "open" + }, + { + "TEXT": "Løst", + "VALUE": "resolved" + }, + { + "TEXT": "Bot", + "VALUE": "bot" + } + ], + "ATTACHMENTS": { + "image": { + "ICON": "ion-image", + "CONTENT": "Bildemelding" + }, + "audio": { + "ICON": "ion-volume-high", + "CONTENT": "Lydmelding" + }, + "video": { + "ICON": "ion-ios-videocam", + "CONTENT": "Video beskjed" + }, + "file": { + "ICON": "ion-document", + "CONTENT": "Vedlegg" + }, + "location": { + "ICON": "ion-ios-location", + "CONTENT": "Plassering" + }, + "fallback": { + "ICON": "ion-link", + "CONTENT": "har delt en URL" + } + }, + "RECEIVED_VIA_EMAIL": "Mottatt via e-post", + "VIEW_TWEET_IN_TWITTER": "Vis tweet i Twitter", + "REPLY_TO_TWEET": "Svar på denne tweeten", + "NO_MESSAGES": "Ingen meldinger" + } +} diff --git a/app/javascript/dashboard/i18n/locale/no/contact.json b/app/javascript/dashboard/i18n/locale/no/contact.json new file mode 100644 index 000000000..a44e2ac23 --- /dev/null +++ b/app/javascript/dashboard/i18n/locale/no/contact.json @@ -0,0 +1,115 @@ +{ + "CONTACT_PANEL": { + "NOT_AVAILABLE": "Ikke tilgjengelig", + "EMAIL_ADDRESS": "E-postadresse", + "PHONE_NUMBER": "Telefonnummer", + "COPY_SUCCESSFUL": "Kopiert til utklippstavle", + "COMPANY": "Firma", + "LOCATION": "Plassering", + "CONVERSATION_TITLE": "Samtaledetaljer", + "BROWSER": "Nettleser", + "OS": "Operativsystem", + "INITIATED_FROM": "Startet fra", + "INITIATED_AT": "Started", + "IP_ADDRESS": "IP Adresse", + "CONVERSATIONS": { + "NO_RECORDS_FOUND": "Det er ingen tidligere samtaler knyttet til denne kontakten.", + "TITLE": "Tidligere samtaler" + }, + "CUSTOM_ATTRIBUTES": { + "TITLE": "Egendefinerte verdier" + }, + "LABELS": { + "TITLE": "Samtaleetiketter", + "MODAL": { + "TITLE": "Etiketter for", + "ACTIVE_LABELS": "Etiketter lagt til i samtalen", + "INACTIVE_LABELS": "Etiketter tilgjengelig i kontoen", + "REMOVE": "Klikk på X ikonet for å fjerne etiketten", + "ADD": "Klikk på + ikonet for å legge til etiketten", + "UPDATE_BUTTON": "Oppdater etiketter", + "UPDATE_ERROR": "Kunne ikke oppdatere etiketter, prøv på nytt." + }, + "NO_LABELS_TO_ADD": "Det er ingen flere etiketter definert i kontoen.", + "NO_AVAILABLE_LABELS": "Det er ingen etiketter lagt til i denne samtalen." + }, + "MUTE_CONTACT": "Demp samtale", + "UNMUTE_CONTACT": "Skru av demping", + "MUTED_SUCCESS": "Denne samtalen er dempet i 6 timer", + "UNMUTED_SUCCESS": "Demping er skrudd av for samtalen", + "SEND_TRANSCRIPT": "Send kopi av samtale", + "EDIT_LABEL": "Rediger" + }, + "EDIT_CONTACT": { + "BUTTON_LABEL": "Rediger kontakt", + "TITLE": "Rediger kontakt", + "DESC": "Rediger kontaktopplysninger", + "FORM": { + "SUBMIT": "Send", + "CANCEL": "Avbryt", + "AVATAR": { + "LABEL": "Kontaktens profilbilde" + }, + "NAME": { + "PLACEHOLDER": "Angi hele navnet på kontakten", + "LABEL": "Fullt navn" + }, + "BIO": { + "PLACEHOLDER": "Skriv beskrivelse for kontakten", + "LABEL": "Beskrivelse" + }, + "EMAIL_ADDRESS": { + "PLACEHOLDER": "Skriv inn e-postadressen til kontakten", + "LABEL": "E-postadresse" + }, + "PHONE_NUMBER": { + "PLACEHOLDER": "Skriv inn telefonnummeret til kontakten", + "LABEL": "Telefonnummer" + }, + "LOCATION": { + "PLACEHOLDER": "Angi plasseringen til kontakten", + "LABEL": "Plassering" + }, + "COMPANY_NAME": { + "PLACEHOLDER": "Skriv inn firmanavn", + "LABEL": "Firmaets navn" + }, + "SOCIAL_PROFILES": { + "FACEBOOK": { + "PLACEHOLDER": "Skriv inn Facebook brukernavn", + "LABEL": "Facebook" + }, + "TWITTER": { + "PLACEHOLDER": "Skriv inn Twitter brukernavn", + "LABEL": "Twitter" + }, + "LINKEDIN": { + "PLACEHOLDER": "Skriv inn LinkedIn brukernavn", + "LABEL": "LinkedIn" + }, + "GITHUB": { + "PLACEHOLDER": "Skriv inn Github brukernavn", + "LABEL": "Github" + } + } + }, + "SUCCESS_MESSAGE": "Kontakten er oppdatert", + "CONTACT_ALREADY_EXIST": "Denne e-postadressen er allerede i bruk av en annen kontakt.", + "ERROR_MESSAGE": "Det oppstod en feil under oppdatering av kontakten. Prøv igjen" + }, + "CONTACTS_PAGE": { + "HEADER": "Kontakter", + "SEARCH_BUTTON": "Søk", + "SEARCH_INPUT_PLACEHOLDER": "Søk etter kontakter", + "LIST": { + "LOADING_MESSAGE": "Laster inn kontakter...", + "404": "Ingen kontakter samsvarer med søket ditt 🔍", + "TABLE_HEADER": [ + "Navn", + "Telefonnummer", + "Samtaler", + "Siste kontakt" + ] + } + } +} diff --git a/app/javascript/dashboard/i18n/locale/no/conversation.json b/app/javascript/dashboard/i18n/locale/no/conversation.json new file mode 100644 index 000000000..c0c523a73 --- /dev/null +++ b/app/javascript/dashboard/i18n/locale/no/conversation.json @@ -0,0 +1,78 @@ +{ + "CONVERSATION": { + "404": "Velg en samtale fra venstre panel", + "NO_MESSAGE_1": "Å nei! Ser ut til at det ikke er noen meldinger fra kunder i innboksen din.", + "NO_MESSAGE_2": " for å sende en melding til siden din!", + "NO_INBOX_1": "Tjohei! Det ser ut til at du ikke har lagt til noen innbokser enda.", + "NO_INBOX_2": " for å komme i gang", + "NO_INBOX_AGENT": "Uff da! Det ser ut til at du ikke er en del av innboksen. Kontakt systemansvarlig", + "SEARCH_MESSAGES": "Søk etter meldinger i samtaler", + "SEARCH": { + "TITLE": "Søk i meldinger", + "LOADING_MESSAGE": "Knuser data...", + "PLACEHOLDER": "Skriv tekst for å søke etter meldinger", + "NO_MATCHING_RESULTS": "Det finnes ingen meldinger som samsvarer med søket." + }, + "UNREAD_MESSAGES": "Uleste meldinger", + "UNREAD_MESSAGE": "Ulest melding", + "CLICK_HERE": "Klikk her", + "LOADING_INBOXES": "Laster innbokser", + "LOADING_CONVERSATIONS": "Laster samtaler", + "CANNOT_REPLY": "Du kan ikke svare på grunn av", + "24_HOURS_WINDOW": "24-timers meldingsrestriksjon", + "LAST_INCOMING_TWEET": "Du svarer på den siste innkommende tweet", + "REPLYING_TO": "Du svarer til:", + "REMOVE_SELECTION": "Fjern utvalget", + "DOWNLOAD": "Last ned", + "UPLOADING_ATTACHMENTS": "Laster opp vedlegg...", + "NO_RESPONSE": "No response", + "HEADER": { + "RESOLVE_ACTION": "Løs", + "REOPEN_ACTION": "Gjenåpne", + "OPEN": "Mer", + "CLOSE": "Lukk", + "DETAILS": "detaljer" + }, + "FOOTER": { + "MSG_INPUT": "Shift + enter for ny linje. Start med '/' for å velge et forhåndslagd svar.", + "PRIVATE_MSG_INPUT": "Skift + enter for ny linje. Dette vil kun være synlig for agenter" + }, + "REPLYBOX": { + "REPLY": "Svar", + "PRIVATE_NOTE": "Privat notat", + "SEND": "Send", + "CREATE": "Legg til notat", + "TWEET": "Tweet", + "TIP_FORMAT_ICON": "Vis rik tekstredigering", + "TIP_EMOJI_ICON": "Vis emoji-velger", + "TIP_ATTACH_ICON": "Legg ved filer", + "ENTER_TO_SEND": "Trykk enter for å sende" + }, + "VISIBLE_TO_AGENTS": "Privat notat: bare synlig for deg og ditt team", + "CHANGE_STATUS": "Samtalestatus er endret", + "CHANGE_AGENT": "Tildelt agent er endret", + "SENT_BY": "Sendt av:", + "ASSIGNMENT": { + "SELECT_AGENT": "Velg agent", + "REMOVE": "Fjern", + "ASSIGN": "Tildel" + } + }, + "EMAIL_TRANSCRIPT": { + "TITLE": "Send kopi av samtale", + "DESC": "Send en kopi av samtalen til den angitte e-postadressen", + "SUBMIT": "Send", + "CANCEL": "Avbryt", + "SEND_EMAIL_SUCCESS": "Kopi av samtalen ble sendt", + "SEND_EMAIL_ERROR": "Det oppstod en feil. Prøv igjen", + "FORM": { + "SEND_TO_CONTACT": "Send kopi av samtalen til kunden", + "SEND_TO_AGENT": "Send kopi av samtalen til den tildelte agenten", + "SEND_TO_OTHER_EMAIL_ADDRESS": "Send kopi av samtalen til en annen e-postadresse", + "EMAIL": { + "PLACEHOLDER": "Skriv inn en e-postadresse", + "ERROR": "Vennligst skriv inn en gyldig e-postadresse" + } + } + } +} diff --git a/app/javascript/dashboard/i18n/locale/no/generalSettings.json b/app/javascript/dashboard/i18n/locale/no/generalSettings.json new file mode 100644 index 000000000..0a217efa4 --- /dev/null +++ b/app/javascript/dashboard/i18n/locale/no/generalSettings.json @@ -0,0 +1,75 @@ +{ + "GENERAL_SETTINGS": { + "TITLE": "Kontoinnstillinger", + "SUBMIT": "Oppdater innstillinger", + "BACK": "Tilbake", + "UPDATE": { + "ERROR": "Kunne ikke oppdatere innstillinger, prøv igjen!", + "SUCCESS": "Innstillinger ble oppdatert" + }, + "FORM": { + "ERROR": "Vennligst fiks skjemafeil", + "GENERAL_SECTION": { + "TITLE": "Generelle innstillinger", + "NOTE": "" + }, + "NAME": { + "LABEL": "Kontonavn", + "PLACEHOLDER": "Ditt kontonavn", + "ERROR": "Vennligst oppgi et gyldig kontonavn" + }, + "LANGUAGE": { + "LABEL": "Nettstedspråk (Beta)", + "PLACEHOLDER": "Ditt kontonavn", + "ERROR": "" + }, + "DOMAIN": { + "LABEL": "Innkommende e-postdomene", + "PLACEHOLDER": "Domenet hvor du vil motta e-posten", + "ERROR": "" + }, + "SUPPORT_EMAIL": { + "LABEL": "Support e-post", + "PLACEHOLDER": "Ditt firmas support e-post", + "ERROR": "" + }, + "AUTO_RESOLVE_DURATION": { + "LABEL": "Antall dager en sak skal løses automatisk hvis det ikke har vært aktivitet", + "PLACEHOLDER": "30", + "ERROR": "Angi en gyldig varighet for når en sak skal løses automatisk (minimum 1 dag)" + }, + "FEATURES": { + "INBOUND_EMAIL_ENABLED": "Samtalekontinuitet med e-post er aktivert for din konto.", + "CUSTOM_EMAIL_DOMAIN_ENABLED": "Du kan motta e-post i ditt egendefinerte domene nå." + } + }, + "UPDATE_CHATWOOT": "En oppdatering av %{latestChatwootVersion} for Chatwoot er tilgjengelig. Oppdater din instans." + }, + "FORMS": { + "MULTISELECT": { + "ENTER_TO_SELECT": "Trykk enter for å velge", + "ENTER_TO_REMOVE": "Trykk enter for å fjerne", + "SELECT_ONE": "Velg en" + } + }, + "NOTIFICATIONS_PAGE": { + "HEADER": "Varsler", + "MARK_ALL_DONE": "Merk alle som ferdige", + "LIST": { + "LOADING_MESSAGE": "Laster varsler...", + "404": "Ingen varsler", + "TABLE_HEADER": [ + "Navn", + "Telefonnummer", + "Samtaler", + "Siste kontakt" + ] + }, + "TYPE_LABEL": { + "conversation_creation": "Ny samtale", + "conversation_assignment": "Samtale tildelt", + "assigned_conversation_new_message": "Ny melding", + "conversation_mention": "Omtale" + } + } +} diff --git a/app/javascript/dashboard/i18n/locale/no/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/no/inboxMgmt.json new file mode 100644 index 000000000..5f68c594d --- /dev/null +++ b/app/javascript/dashboard/i18n/locale/no/inboxMgmt.json @@ -0,0 +1,255 @@ +{ + "INBOX_MGMT": { + "HEADER": "Innbokser", + "SIDEBAR_TXT": "Innboks
Når du kobler til et nettsted eller en facebook-side til Chatwoot blir den kalt en Innboks. Du kan ha ubegrensede innbokser i din Chatwoot-konto.
Klikk på Legg til innboks for å koble sammen en nettside eller en facebook-side.
I oversikten kan du se alle samtalene fra innboksene på et sted og svare på de i samtaler-fanen.
Du kan også se samtaler for hver enkelt innboks ved å klikke på innboksen på venstre siden av oversikten.
", + "LIST": { + "404": "Det er ingen innbokser tilknyttet denne kontoen." + }, + "CREATE_FLOW": [ + { + "title": "Velg kanal", + "route": "settings_inbox_new", + "body": "Velg tilbyderen du vil integrere med Chatwoot." + }, + { + "title": "Opprett innboks", + "route": "settings_inboxes_page_channel", + "body": "Autoriser din konto og opprett en innboks." + }, + { + "title": "Legg til agenter", + "route": "settings_inboxes_add_agents", + "body": "Legg agenter til i den opprettede innboksen." + }, + { + "title": "Voila!", + "route": "settings_inbox_finish", + "body": "Klar - ferdig - gå!" + } + ], + "ADD": { + "FB": { + "HELP": "PS: Ved å logge inn får vi bare tilgang til meldingene til siden. Dine private meldinger vil aldri bli lest av Chatwoot.", + "CHOOSE_PAGE": "Velg side", + "CHOOSE_PLACEHOLDER": "Velg en side fra listen", + "INBOX_NAME": "Navn på innboks", + "ADD_NAME": "Legge til et navn på innboksen din", + "PICK_NAME": "Velg et navn din innboks", + "PICK_A_VALUE": "Velg en verdi" + }, + "TWITTER": { + "HELP": "For å legge til din Twitter-profil som kanal, må du autorisere din Twitter-profil ved å klikke på 'Logg inn med Twitter' " + }, + "WEBSITE_CHANNEL": { + "TITLE": "Nettsidekanal", + "DESC": "Opprett en kanal for nettstedet ditt og begynn å hjelpe kundene dine via nettstedswidgeten.", + "LOADING_MESSAGE": "Oppretter nettstedskanal", + "CHANNEL_AVATAR": { + "LABEL": "Kanal profilbilde" + }, + "CHANNEL_NAME": { + "LABEL": "Nettstedsnavn", + "PLACEHOLDER": "Angi navnet på nettsiden (f. eks.: Acme Inc)" + }, + "CHANNEL_DOMAIN": { + "LABEL": "Nettstedets domene", + "PLACEHOLDER": "Angi URL på nettsiden (f. eks.: acme.no)" + }, + "CHANNEL_WELCOME_TITLE": { + "LABEL": "Velkomsttittel", + "PLACEHOLDER": "Tjohei!" + }, + "CHANNEL_WELCOME_TAGLINE": { + "LABEL": "Velkomsthilsen undertittel", + "PLACEHOLDER": "Det skal være lett å kontakte oss. Spør oss om hva som helst, eller del din tilbakemelding." + }, + "CHANNEL_GREETING_MESSAGE": { + "LABEL": "Kanalhilsen", + "PLACEHOLDER": "Acme AS pleier vanligvis å svare i løpet av noen timer." + }, + "CHANNEL_GREETING_TOGGLE": { + "LABEL": "Aktiver kanalhilsen", + "HELP_TEXT": "Send en velkomstmelding til brukeren når hen starter samtalen.", + "ENABLED": "Aktivert", + "DISABLED": "Deaktivert" + }, + "REPLY_TIME": { + "TITLE": "Angi svartid", + "IN_A_FEW_MINUTES": "Om noen få minutter", + "IN_A_FEW_HOURS": "Om noen få timer", + "IN_A_DAY": "Om en dag", + "HELP_TEXT": "Denne svartiden vil bli vist i live chat widgeten" + }, + "WIDGET_COLOR": { + "LABEL": "Widget farge", + "PLACEHOLDER": "Oppdater fargen brukt i widgeten" + }, + "SUBMIT_BUTTON": "Opprett innboks" + }, + "TWILIO": { + "TITLE": "Twilio SMS/Whatsapp kanal", + "DESC": "Integrer Twilio for å støtte kundene dine via SMS eller Whatsapp.", + "ACCOUNT_SID": { + "LABEL": "Konto-SID", + "PLACEHOLDER": "Skriv inn din Twilio konto-SID", + "ERROR": "Dette feltet er obligatorisk" + }, + "CHANNEL_TYPE": { + "LABEL": "Kanaltype", + "ERROR": "Velg din kanaltype" + }, + "AUTH_TOKEN": { + "LABEL": "Autoriseringstoken", + "PLACEHOLDER": "Vennligst skriv inn din Twilio autoriseringstoken", + "ERROR": "Dette feltet er obligatorisk" + }, + "CHANNEL_NAME": { + "LABEL": "Kanalnavn", + "PLACEHOLDER": "Vennligst skriv inn et kanalnavn", + "ERROR": "Dette feltet er obligatorisk" + }, + "PHONE_NUMBER": { + "LABEL": "Telefonnummer", + "PLACEHOLDER": "Vennligst angi telefonnummeret som meldinger skal sendes fra.", + "ERROR": "Skriv inn en gyldig verdi. Telefonnummeret skal starte med `+`-tegn." + }, + "API_CALLBACK": { + "TITLE": "Callback URL", + "SUBTITLE": "Du må konfigurere callback URL for meldinger med URLen nevnt her." + }, + "SUBMIT_BUTTON": "Opprett Twilio-kanal", + "API": { + "ERROR_MESSAGE": "Vi kunne ikke godkjenne Twilio legitimasjonsopplysningene, vennligst prøv igjen" + } + }, + "API_CHANNEL": { + "TITLE": "API Kanal", + "DESC": "Integrer med en API-kanal for å støtte dine kunder.", + "CHANNEL_NAME": { + "LABEL": "Kanalnavn", + "PLACEHOLDER": "Vennligst skriv inn et kanalnavn", + "ERROR": "Dette feltet er obligatorisk" + }, + "WEBHOOK_URL": { + "LABEL": "Webhook URL", + "SUBTITLE": "Konfigurere nettadressen du vil motta callbacks fra hendelser.", + "PLACEHOLDER": "Webhook URL" + }, + "SUBMIT_BUTTON": "Opprett API-kanal", + "API": { + "ERROR_MESSAGE": "Vi klarte ikke å lagre API-kanalen" + } + }, + "EMAIL_CHANNEL": { + "TITLE": "E-postkanal", + "DESC": "Integrer din e-postinnboks.", + "CHANNEL_NAME": { + "LABEL": "Kanalnavn", + "PLACEHOLDER": "Vennligst skriv inn et kanalnavn", + "ERROR": "Dette feltet er obligatorisk" + }, + "EMAIL": { + "LABEL": "E-post", + "SUBTITLE": "E-post der kundene dine sender deg henvendelser", + "PLACEHOLDER": "E-post" + }, + "SUBMIT_BUTTON": "Opprett e-postkanal", + "API": { + "ERROR_MESSAGE": "Vi kunne ikke lagre e-postkanalen" + }, + "FINISH_MESSAGE": "Begynn å videresende e-post til følgende e-postadresse." + }, + "AUTH": { + "TITLE": "Kanaler", + "DESC": "Vi støtter for tiden live chat widgets, Facebook sider, Twitter profiler og Whatsapp. Vi har flere plattformer er på vei som e-post, Telegram og Line. Disse vil lanseres snart." + }, + "AGENTS": { + "TITLE": "Agenter", + "DESC": "Her kan du legge til agenter for å administrere innboksen din. Bare de valgte agentene har tilgang til innboksen din. Agenter som ikke er en del av denne innboksen vil ikke kunne se eller svare på meldinger i denne innboksen når de logger inn.Webhooks
Webhooks er HTTP callbacks, som kan defineres for hver konto. De utløses av hendelser som opprettelse av meldinger i Chatwoot. Du kan opprette mer enn en webhook for denne kontoen.
For å opprette en webhook, klikk på Legg til ny webhook. Du kan også fjerne en eksisterende webhook ved å klikke på slett.
Etiketter
hjelper deg med å kategorisere samtaler og prioritere dem. Du kan tilordne etiketter til en samtale fra sidepanelet.
Etiketter er knyttet til kontoen og kan brukes til å opprette egendefinerte arbeidsstrømmer i din organisasjon. Du kan tilordne egendefinert farge til en etikett- det gjør det enklere å identifisere etiketten. Du vil kunne vise etiketten på sidepanelet for å enkelt filtrere samtalene.
Labels
Labels help you to categorize conversations and prioritize them. You can assign label to a conversation from the sidepanel.
Labels are tied to the account and can be used to create custom workflows in your organization. You can assign custom color to a label, it makes it easier to identify the label. You will be able to display the label on the sidebar to filter the conversations easily.
Etiquetas
As etiquetas o ajudam a categorizar as conversas e a priorizá-las. Você pode atribuir a etiqueta a uma conversa a partir do painel lateral.
As etiquetas estão vinculadas à conta e podem ser usadas para criar fluxos de trabalho personalizados em sua organização. Você pode atribuir uma cor personalizada para uma etiqueta, facilitando a identificação da etiqueta. Você será capaz de exibir o rótulo na barra lateral para filtrar as conversas facilmente.