From 5ae3026125e8b74ba18c0be6a5d1652340c95be1 Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Wed, 16 Sep 2020 11:46:07 +0530 Subject: [PATCH 01/57] fix: Add available_name method to agent_bot (#1238) --- app/models/agent_bot.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/agent_bot.rb b/app/models/agent_bot.rb index 493c0ed0b..6f849b0f8 100644 --- a/app/models/agent_bot.rb +++ b/app/models/agent_bot.rb @@ -19,6 +19,10 @@ class AgentBot < ApplicationRecord has_many :inboxes, through: :agent_bot_inboxes has_many :messages, as: :sender, dependent: :restrict_with_exception + def available_name + name + end + def push_event_data(inbox = nil) { id: id, From 8b953917e12c2b1496fcbdcb41dba2341ac9983c Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Thu, 17 Sep 2020 13:23:23 +0530 Subject: [PATCH 02/57] chore: Upgrade selfsigned to fix CVE-2020-7720 (#1242) --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index ae9db1f73..48cb5462b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7271,10 +7271,10 @@ node-cache@^4.1.1: clone "2.x" lodash "^4.17.15" -node-forge@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" - integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ== +node-forge@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== node-gyp@^3.8.0: version "3.8.0" @@ -9458,11 +9458,11 @@ select-hose@^2.0.0: integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= selfsigned@^1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b" - integrity sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA== + version "1.10.8" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30" + integrity sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w== dependencies: - node-forge "0.9.0" + node-forge "^0.10.0" semver-compare@^1.0.0: version "1.0.0" From 646746aa108ce90127367c2c439ef25b98ec68c9 Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Thu, 17 Sep 2020 23:32:19 +0530 Subject: [PATCH 03/57] fix: Check medium to decide 24 hour window (#1245) --- app/models/channel/twilio_sms.rb | 4 ++-- spec/models/channel/twilio_sms_spec.rb | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 spec/models/channel/twilio_sms_spec.rb diff --git a/app/models/channel/twilio_sms.rb b/app/models/channel/twilio_sms.rb index 3174409f9..8f25fec2e 100644 --- a/app/models/channel/twilio_sms.rb +++ b/app/models/channel/twilio_sms.rb @@ -31,10 +31,10 @@ class Channel::TwilioSms < ApplicationRecord has_one :inbox, as: :channel, dependent: :destroy def name - medium == :sms ? 'Twilio SMS' : 'Whatsapp' + medium == 'sms' ? 'Twilio SMS' : 'Whatsapp' end def has_24_hour_messaging_window? - true + medium == 'whatsapp' end end diff --git a/spec/models/channel/twilio_sms_spec.rb b/spec/models/channel/twilio_sms_spec.rb new file mode 100644 index 000000000..8aab34a7a --- /dev/null +++ b/spec/models/channel/twilio_sms_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Channel::TwilioSms do + context 'with medium whatsapp' do + let!(:whatsapp_channel) { create(:channel_twilio_sms, medium: :whatsapp) } + + it 'returns true' do + expect(whatsapp_channel.has_24_hour_messaging_window?).to eq true + expect(whatsapp_channel.name).to eq 'Whatsapp' + expect(whatsapp_channel.medium).to eq 'whatsapp' + end + end + + context 'with medium sms' do + let!(:sms_channel) { create(:channel_twilio_sms, medium: :sms) } + + it 'returns false' do + expect(sms_channel.has_24_hour_messaging_window?).to eq false + expect(sms_channel.name).to eq 'Twilio SMS' + expect(sms_channel.medium).to eq 'sms' + end + end +end From 74d07c876efa7daa24015348d9f52e97a2e7e74f Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Fri, 18 Sep 2020 18:50:53 +0530 Subject: [PATCH 04/57] chore: Fix RestClient::GatewayTimeout, label_list of NilClass (#1243) --- app/controllers/api/v1/widget/labels_controller.rb | 12 ++++++++---- app/jobs/contact_avatar_job.rb | 2 +- lib/webhooks/trigger.rb | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/v1/widget/labels_controller.rb b/app/controllers/api/v1/widget/labels_controller.rb index e8e409244..c6435da4b 100644 --- a/app/controllers/api/v1/widget/labels_controller.rb +++ b/app/controllers/api/v1/widget/labels_controller.rb @@ -1,14 +1,18 @@ class Api::V1::Widget::LabelsController < Api::V1::Widget::BaseController def create - conversation.label_list.add(permitted_params[:label]) - conversation.save! + if conversation.present? + conversation.label_list.add(permitted_params[:label]) + conversation.save! + end head :no_content end def destroy - conversation.label_list.remove(permitted_params[:id]) - conversation.save! + if conversation.present? + conversation.label_list.remove(permitted_params[:id]) + conversation.save! + end head :no_content end diff --git a/app/jobs/contact_avatar_job.rb b/app/jobs/contact_avatar_job.rb index e1de371fd..f381b63f2 100644 --- a/app/jobs/contact_avatar_job.rb +++ b/app/jobs/contact_avatar_job.rb @@ -5,6 +5,6 @@ class ContactAvatarJob < ApplicationJob avatar_resource = LocalResource.new(avatar_url) contact.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding) rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED, SocketError, NoMethodError => e - Rails.logger.info "invalid url #{avatar_url} : #{e.message}" + Rails.logger.info "Exception: invalid avatar url #{avatar_url} : #{e.message}" end end diff --git a/lib/webhooks/trigger.rb b/lib/webhooks/trigger.rb index f3fe055dc..5475b75f7 100644 --- a/lib/webhooks/trigger.rb +++ b/lib/webhooks/trigger.rb @@ -1,8 +1,8 @@ class Webhooks::Trigger def self.execute(url, payload) RestClient.post(url, payload.to_json, { content_type: :json, accept: :json }) - rescue RestClient::NotFound => e - Rails.logger.info "invalid url #{url} : #{e.message}" + rescue RestClient::NotFound, RestClient::GatewayTimeout, SocketError => e + Rails.logger.info "Exception: invalid webhook url #{url} : #{e.message}" rescue StandardError => e Raven.capture_exception(e) end From fc7b84d61259aa5af7e739fb4dd12beea1762222 Mon Sep 17 00:00:00 2001 From: Sony Mathew Date: Sat, 19 Sep 2020 12:46:34 +0530 Subject: [PATCH 05/57] Chore: Fix N+1 queries in dashboard side (#1254) * Chore: Fix N+1 queries in dashboard side Fixed a couple of N+1 queries fired on the dashboard side of the app to improve performance. --- .../api/v1/accounts/contacts/conversations_controller.rb | 2 +- app/controllers/api/v1/accounts/contacts_controller.rb | 2 +- app/finders/conversation_finder.rb | 2 +- app/finders/message_finder.rb | 2 +- .../api/v1/conversations/partials/_conversation.json.jbuilder | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/v1/accounts/contacts/conversations_controller.rb b/app/controllers/api/v1/accounts/contacts/conversations_controller.rb index 5c7bf77d7..8a9199b6b 100644 --- a/app/controllers/api/v1/accounts/contacts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/contacts/conversations_controller.rb @@ -1,7 +1,7 @@ class Api::V1::Accounts::Contacts::ConversationsController < Api::V1::Accounts::BaseController def index @conversations = Current.account.conversations.includes( - :assignee, :contact, :inbox + :assignee, :contact, :inbox, :taggings ).where(inbox_id: inbox_ids, contact_id: permitted_params[:contact_id]) end diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb index 87b3898f1..34ac91b3a 100644 --- a/app/controllers/api/v1/accounts/contacts_controller.rb +++ b/app/controllers/api/v1/accounts/contacts_controller.rb @@ -63,6 +63,6 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController end def fetch_contact - @contact = Current.account.contacts.find(params[:id]) + @contact = Current.account.contacts.includes(contact_inboxes: [:inbox]).find(params[:id]) end end diff --git a/app/finders/conversation_finder.rb b/app/finders/conversation_finder.rb index 5ca1e12f6..183c9b535 100644 --- a/app/finders/conversation_finder.rb +++ b/app/finders/conversation_finder.rb @@ -62,7 +62,7 @@ class ConversationFinder def find_all_conversations @conversations = current_account.conversations.includes( - :assignee, :inbox, contact: [:avatar_attachment] + :assignee, :inbox, :taggings, contact: [:avatar_attachment] ).where(inbox_id: @inbox_ids) end diff --git a/app/finders/message_finder.rb b/app/finders/message_finder.rb index 00ef8ab3b..5295dce74 100644 --- a/app/finders/message_finder.rb +++ b/app/finders/message_finder.rb @@ -11,7 +11,7 @@ class MessageFinder private def conversation_messages - @conversation.messages.includes(:attachments, user: { avatar_attachment: :blob }) + @conversation.messages.includes(:attachments, :sender) end def messages diff --git a/app/views/api/v1/conversations/partials/_conversation.json.jbuilder b/app/views/api/v1/conversations/partials/_conversation.json.jbuilder index deae107c1..d9068fe26 100644 --- a/app/views/api/v1/conversations/partials/_conversation.json.jbuilder +++ b/app/views/api/v1/conversations/partials/_conversation.json.jbuilder @@ -12,9 +12,9 @@ end json.id conversation.display_id if conversation.unread_incoming_messages.count.zero? - json.messages [conversation.messages.last.try(:push_event_data)] + json.messages [conversation.messages.includes([{ attachments: [{ file_attachment: [:blob] }] }]).last.try(:push_event_data)] else - json.messages conversation.unread_messages.includes([:user, :attachments]).map(&:push_event_data) + json.messages conversation.unread_messages.includes([:user, { attachments: [{ file_attachment: [:blob] }] }]).map(&:push_event_data) end json.inbox_id conversation.inbox_id From cdd428f50347d229a86accc66ce560412eb5b57f Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Sun, 20 Sep 2020 19:10:08 +0530 Subject: [PATCH 06/57] chore: Add browser logs and server logs to the template (#1260) --- .github/ISSUE_TEMPLATE/bug_report.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 66e142ad6..d9409af96 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -26,6 +26,14 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. +**Browser logs** + +Share the browser logs to debug the issue further + +**Server logs** + +Share the server logs to debug the issue further + **Environment** Describe whether you are using Chatwoot Cloud (app.chatwoot.com) or a self hosted installation of Chatwoot. If you are using a self hosted installation of Chatwoot describe the type of deployment (Docker/Linux VM installation/Heroku) From 0deb1af85212e1ab428eee2ee165af36729b5daf Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Sun, 20 Sep 2020 19:29:39 +0530 Subject: [PATCH 07/57] chore: Add a check for defined labels in SDK API (#1259) --- .../api/v1/widget/labels_controller.rb | 7 ++++++- .../api/v1/widget/labels_controller_spec.rb | 20 +++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/v1/widget/labels_controller.rb b/app/controllers/api/v1/widget/labels_controller.rb index c6435da4b..564fec8fc 100644 --- a/app/controllers/api/v1/widget/labels_controller.rb +++ b/app/controllers/api/v1/widget/labels_controller.rb @@ -1,6 +1,6 @@ class Api::V1::Widget::LabelsController < Api::V1::Widget::BaseController def create - if conversation.present? + if conversation.present? && label_defined_in_account? conversation.label_list.add(permitted_params[:label]) conversation.save! end @@ -19,6 +19,11 @@ class Api::V1::Widget::LabelsController < Api::V1::Widget::BaseController private + def label_defined_in_account? + label = @account.labels.find_by(title: permitted_params[:label]) + label.present? + end + def permitted_params params.permit(:id, :label, :website_token) end diff --git a/spec/controllers/api/v1/widget/labels_controller_spec.rb b/spec/controllers/api/v1/widget/labels_controller_spec.rb index 13ee6f18e..bdd47f9c9 100644 --- a/spec/controllers/api/v1/widget/labels_controller_spec.rb +++ b/spec/controllers/api/v1/widget/labels_controller_spec.rb @@ -12,8 +12,24 @@ RSpec.describe '/api/v1/widget/labels', type: :request do describe 'POST /api/v1/widget/labels' do let(:params) { { website_token: web_widget.website_token, label: 'customer-support' } } - context 'with correct website token' do - it 'returns the list of labels' do + context 'with correct website token and undefined label' do + it 'does not add the label' do + post '/api/v1/widget/labels', + params: params, + headers: { 'X-Auth-Token' => token }, + as: :json + + expect(response).to have_http_status(:success) + expect(conversation.reload.label_list.count).to eq 0 + end + end + + context 'with correct website token and a defined label' do + before do + account.labels.create(title: 'customer-support') + end + + it 'add the label to the conversation' do post '/api/v1/widget/labels', params: params, headers: { 'X-Auth-Token' => token }, From ea6577af6de427d9ddc29015d0939bc03a6f5df8 Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Sun, 20 Sep 2020 22:19:34 +0530 Subject: [PATCH 08/57] fix: Remove agent keys from translation file (#1261) --- .../dashboard/components/layout/Sidebar.vue | 5 +- .../dashboard/i18n/locale/ar/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/ca/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/cs/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/da/agentMgmt.json | 26 +++----- .../dashboard/i18n/locale/de/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/el/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/en/agentMgmt.json | 26 +++----- .../dashboard/i18n/locale/es/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/fa/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/fi/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/fr/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/hi/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/hu/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/it/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/ja/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/ko/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/ml/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/nl/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/pl/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/pt/agentMgmt.json | 14 ++--- .../i18n/locale/pt_BR/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/ro/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/ru/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/sk/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/sv/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/ta/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/th/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/tr/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/uk/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/vi/agentMgmt.json | 14 ++--- .../dashboard/i18n/locale/zh/agentMgmt.json | 14 ++--- .../i18n/locale/zh_CN/agentMgmt.json | 14 ++--- .../i18n/locale/zh_TW/agentMgmt.json | 14 ++--- .../dashboard/settings/agents/AddAgent.vue | 36 +++++------ .../dashboard/settings/agents/EditAgent.vue | 63 +++++++++++-------- .../dashboard/settings/agents/Index.vue | 8 ++- .../dashboard/settings/canned/AddCanned.vue | 27 ++------ 38 files changed, 210 insertions(+), 415 deletions(-) diff --git a/app/javascript/dashboard/components/layout/Sidebar.vue b/app/javascript/dashboard/components/layout/Sidebar.vue index f13fbee92..a491af043 100644 --- a/app/javascript/dashboard/components/layout/Sidebar.vue +++ b/app/javascript/dashboard/components/layout/Sidebar.vue @@ -65,8 +65,8 @@

{{ currentUserAvailableName }}

-
- {{ currentRole }} +
+ {{ $t(`AGENT_MGMT.AGENT_TYPES.${currentRole.toUpperCase()}`) }}
@@ -162,7 +162,6 @@ import Thumbnail from '../widgets/Thumbnail'; import { getSidebarItems } from '../../i18n/default-sidebar'; import { required, minLength } from 'vuelidate/lib/validators'; import alertMixin from 'shared/mixins/alertMixin'; -// import accountMixin from '../../../../../mixins/account'; export default { components: { diff --git a/app/javascript/dashboard/i18n/locale/ar/agentMgmt.json b/app/javascript/dashboard/i18n/locale/ar/agentMgmt.json index 906fa2883..a8cd89474 100644 --- a/app/javascript/dashboard/i18n/locale/ar/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ar/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "إضافة موظف", "LOADING": "جار جلب قائمة الموظفين", "SIDEBAR_TXT": "

الموظفين

موظف الدعم هو عضو في فريق دعم العملاء الخاص بك.

يستطيع موظفو الدعم مشاهدة الرسائل الواردة من المستخدمين والرد عليها. تظهر القائمة جميع الموظفين الموجودين حاليا في حسابك.

انقر فوق إضافة موظف لإضافة موظف دعم فني جديد. سيتلقى الشخص الذي تضيفه رسالة بريد إلكتروني مع رابط تأكيد لتفعيل حسابه ، وبعد ذلك يمكنهم الوصول إلى Chatwoot والرد على الرسائل.

الوصول إلى ميزات Chatwoot يتوقف على الصلاحيات التالية.

الموظف - موظفي الدعم الذين لديهم هذه الصلاحية يمكنهم فقط الوصول إلى صناديق قنوات التواصل والتقارير والمحادثات. ويمكنهم بدء محادثات مع موظفين آخرين أو مع أنفسهم وأيضاً إغلاق المحادثات.

مدير البرنامج - الشخص المسؤول من الوصول إلى جميع ميزات Chatwoot المفعلة للحساب الخاص بك. بما في ذلك الإعدادات، إلى جانب جميع امتيازات الموظفين العاديين.

", - "AGENT_TYPES": [ - { - "name": "administrator", - "label": "المدير" - }, - { - "name": "agent", - "label": "موظف الدعم" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "المدير", + "AGENT": "موظف الدعم" + }, "LIST": { "404": "لا يوجد موظفي دعم مرتبطين بهذا الحساب", "TITLE": "إدارة موظفي الدعم في فريقك", diff --git a/app/javascript/dashboard/i18n/locale/ca/agentMgmt.json b/app/javascript/dashboard/i18n/locale/ca/agentMgmt.json index ba4e1caa3..63ada3d82 100644 --- a/app/javascript/dashboard/i18n/locale/ca/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ca/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Afegir Agent", "LOADING": "S'està recollint la llista d'agents", "SIDEBAR_TXT": "

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": [ - { - "name": "administrador/a", - "label": "Administrador/a" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrador/a", + "AGENT": "Agent" + }, "LIST": { "404": "No hi ha agents associats a aquest compte", "TITLE": "Gestiona agents en el teu equip", diff --git a/app/javascript/dashboard/i18n/locale/cs/agentMgmt.json b/app/javascript/dashboard/i18n/locale/cs/agentMgmt.json index b580eaaad..0585a5aed 100644 --- a/app/javascript/dashboard/i18n/locale/cs/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/cs/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Přidat agenta", "LOADING": "Načítání seznamu agentů", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administrátor" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrátor", + "AGENT": "Agent" + }, "LIST": { "404": "K tomuto účtu nejsou přiřazeni žádní agenti", "TITLE": "Spravujte agenty ve vašem týmu", diff --git a/app/javascript/dashboard/i18n/locale/da/agentMgmt.json b/app/javascript/dashboard/i18n/locale/da/agentMgmt.json index 633424fed..ac4a62127 100644 --- a/app/javascript/dashboard/i18n/locale/da/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/da/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Add Agent", "LOADING": "Fetching Agent List", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administrator" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, "LIST": { "404": "There are no agents associated to this account", "TITLE": "Manage agents in your team", @@ -35,9 +29,9 @@ "PLACEHOLDER": "Please enter a name of the agent" }, "AGENT_TYPE": { - "LABEL": "Agent Type", - "PLACEHOLDER": "Please select a type", - "ERROR": "Agent type is required" + "LABEL": "Role", + "PLACEHOLDER": "Please select a role", + "ERROR": "Role is required" }, "EMAIL": { "LABEL": "Email Address", @@ -72,9 +66,9 @@ "PLACEHOLDER": "Please enter a name of the agent" }, "AGENT_TYPE": { - "LABEL": "Agent Type", - "PLACEHOLDER": "Please select a type", - "ERROR": "Agent type is required" + "LABEL": "Role", + "PLACEHOLDER": "Please select a role", + "ERROR": "Role is required" }, "EMAIL": { "LABEL": "Email Address", diff --git a/app/javascript/dashboard/i18n/locale/de/agentMgmt.json b/app/javascript/dashboard/i18n/locale/de/agentMgmt.json index 8ed86701e..343214656 100644 --- a/app/javascript/dashboard/i18n/locale/de/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/de/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Agent hinzufügen", "LOADING": "Agentenliste abrufen", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administrator" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, "LIST": { "404": "Diesem Konto sind keine Agenten zugeordnet", "TITLE": "Verwalten Sie Agenten in Ihrem Team", diff --git a/app/javascript/dashboard/i18n/locale/el/agentMgmt.json b/app/javascript/dashboard/i18n/locale/el/agentMgmt.json index e2463ac6c..80c935285 100644 --- a/app/javascript/dashboard/i18n/locale/el/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/el/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Προσθήκη Πράκτορα", "LOADING": "Λήψη της λίστα των Πρακτόρων", "SIDEBAR_TXT": "

Πράκτορες

Ένας Πράκτορας είναι ένα μέλος της ομάδας υποστήριξής σας.

Οι πράκτορες θα μπορούν να δουν και να απαντήσουν στα μηνύματα των χρηστών. Στην λίστα φαίνονται όλοι οι πράκτορες που συμμετέχουν στον λογαριασμό σας.

Πατήστε στο Προσθήκη Πράκτορα για να προσθέσετε έναν νέο. Ο πράκτορας θα λάβει ένα email με σύνδεσμο επιβεβαίωσης για να ενεργοποιήσει τον λογαριασμό του, ύστερα θα μπορούν να δουν το Chatwoot και να ανταποκρίνονται στα μηνύματά τους.

Η πρόσβαση στις δυνατότητες του Chatwoot βασίζεται στους παρακάτω ρόλους.

Πράκτορας - Οι χρήστες με αυτόν τον ρόλο έχουν πρόσβαση μόνο στα εισερχόμενα, αναφορές και τις συζητήσεις. Μπορούν επίσης να αναθέσουν συζητήσεις σε άλλους πράκτορες ή τον εαυτό τους και να τις ολοκληρώσουν.

Διαχειριστής - Ο διαχειριστής θα έχει πρόσβαση σε όλες τις δυνατότητες του Chatwoot που έχουν ενεργοποιηθεί για τον λογαριασμό, συμπεριλαμβανομένων των ρυθμίσεων της εφαρμογής, όπως επίσης και όλα τα δικαιώματα που έχει ένας πράκτορας.

", - "AGENT_TYPES": [ - { - "name": "administrator", - "label": "Διαχειριστής" - }, - { - "name": "agent", - "label": "Πράκτορας" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Διαχειριστής", + "AGENT": "Πράκτορας" + }, "LIST": { "404": "Δεν υπάρχουν πράκτορες σε αυτόν τον λογαριασμό", "TITLE": "Διαχείριση πρακτόρων της ομάδας σας", diff --git a/app/javascript/dashboard/i18n/locale/en/agentMgmt.json b/app/javascript/dashboard/i18n/locale/en/agentMgmt.json index 633424fed..ac4a62127 100644 --- a/app/javascript/dashboard/i18n/locale/en/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/en/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Add Agent", "LOADING": "Fetching Agent List", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administrator" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, "LIST": { "404": "There are no agents associated to this account", "TITLE": "Manage agents in your team", @@ -35,9 +29,9 @@ "PLACEHOLDER": "Please enter a name of the agent" }, "AGENT_TYPE": { - "LABEL": "Agent Type", - "PLACEHOLDER": "Please select a type", - "ERROR": "Agent type is required" + "LABEL": "Role", + "PLACEHOLDER": "Please select a role", + "ERROR": "Role is required" }, "EMAIL": { "LABEL": "Email Address", @@ -72,9 +66,9 @@ "PLACEHOLDER": "Please enter a name of the agent" }, "AGENT_TYPE": { - "LABEL": "Agent Type", - "PLACEHOLDER": "Please select a type", - "ERROR": "Agent type is required" + "LABEL": "Role", + "PLACEHOLDER": "Please select a role", + "ERROR": "Role is required" }, "EMAIL": { "LABEL": "Email Address", diff --git a/app/javascript/dashboard/i18n/locale/es/agentMgmt.json b/app/javascript/dashboard/i18n/locale/es/agentMgmt.json index 2b629a647..912a4de9d 100644 --- a/app/javascript/dashboard/i18n/locale/es/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/es/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Añadir agente", "LOADING": "Se están listando los agentes", "SIDEBAR_TXT": "

Agentes

Un Agente es miembro de su equipo de Atención al Cliente.

Los agentes podrán ver y responder a los mensajes de sus usuarios. La lista muestra todos los agentes actualmente en tu cuenta.

Haga clic en Añadir agente para añadir un nuevo agente. El agente que añadas recibirá un correo electrónico con un enlace de confirmación para activar su cuenta, después de lo cual podrá acceder a Chatwoot y responder a los mensajes.

El acceso a las características de Chatwoot se basa en los siguientes roles.

Agente - Los agentes con este rol solamente pueden acceder a bandejas, informes y conversaciones. Pueden asignar conversaciones a otros agentes o a sí mismos y resolver conversaciones.

Administrador - El administrador tendrá acceso a todas las características de Chatwoot habilitadas para su cuenta, incluyendo configuración y facturación, junto con todos los privilegios de los agentes normales.

", - "AGENT_TYPES": [ - { - "name": "administrator", - "label": "Administrador" - }, - { - "name": "agent", - "label": "Agente" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrador", + "AGENT": "Agente" + }, "LIST": { "404": "No hay agentes asociados a esta cuenta", "TITLE": "Administrar agentes en tu equipo", diff --git a/app/javascript/dashboard/i18n/locale/fa/agentMgmt.json b/app/javascript/dashboard/i18n/locale/fa/agentMgmt.json index 7e63ce7a9..b20a80a24 100644 --- a/app/javascript/dashboard/i18n/locale/fa/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/fa/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "اضافه کردن اپراتور", "LOADING": "دریافت لیست اپراتورها", "SIDEBAR_TXT": "

اپراتورها

یک اپراتور یکی از اعضای تیم پشتیبانی است.

اپراتورها می‌توانند پیام‌های کاربران را ببینند و به آن‌ها پاسخ بدهند. این لیست حاوی تمام اپراتورهایی است که در حساب شما تعریف شده اند.

با زدن روی دکمه اضافه کردن اپراتور می‌توانید یک اپراتور جدید معرفی کنید. به ایمیل اپراتوری که معرفی می‌کنید یک دعوتنامه ارسال می‌شود که بعد از پذیرفتن آن اپراتور می‌تواند به پیام‌های کاربران پاسخ بدهد.

بسته به سطح دسترسی تعیین شده یک اپراتور می‌تواند به بخش‌های مشخصی از اکانت دسترسی پیدا کند

اپراتور - اپراتورهایی که این نقش را داشته باشند تنها می‌توانند به صندوق‌های ورودی، گزارشات و گفتگوها دسترسی داشته باشند. آن‌ها می‌توانند یک مکالمه را به اپراتور دیگر یا خودشان تخصیص دهند و یا یک مکالمه را حل شده اعلام کنند.

مدیر - مدیران می‌توانند علاوه بر تمام بخش‌هایی که یک اپراتور دسترسی دارد، به تمام بخش‌هایی که در حساب کاربری شما وجود دارد دسترسی داشته باشند.

", - "AGENT_TYPES": [ - { - "name": "administrator", - "label": "مدیر" - }, - { - "name": "agent", - "label": "اپراتور" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "مدیر", + "AGENT": "اپراتور" + }, "LIST": { "404": "در حال حاضر هیچ اپراتوری برای این حساب معرفی نشده است.", "TITLE": "مدیریت اپراتورها", diff --git a/app/javascript/dashboard/i18n/locale/fi/agentMgmt.json b/app/javascript/dashboard/i18n/locale/fi/agentMgmt.json index 633424fed..2b2358c9a 100644 --- a/app/javascript/dashboard/i18n/locale/fi/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/fi/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Add Agent", "LOADING": "Fetching Agent List", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administrator" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, "LIST": { "404": "There are no agents associated to this account", "TITLE": "Manage agents in your team", diff --git a/app/javascript/dashboard/i18n/locale/fr/agentMgmt.json b/app/javascript/dashboard/i18n/locale/fr/agentMgmt.json index 6543ced23..1c6128537 100644 --- a/app/javascript/dashboard/i18n/locale/fr/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/fr/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Ajouter un agent", "LOADING": "Récupération de la liste des agents", "SIDEBAR_TXT": "

Agents

Un agent est un membre de votre équipe d'assistance clientèle.

Les agents pourront voir et répondre aux messages de vos utilisateurs. La liste montre tous les agents actuellement dans votre compte.

Cliquez sur Ajouter un agent pour ajouter un nouvel agent. L'agent que vous ajoutez recevra un courriel avec un lien de confirmation pour activer son compte, après quoi il pourra accéder à Chatwoot et répondre aux messages.

L'accès aux fonctionnalités de Chatwoot est basé sur les rôles suivants.

Agent - Les agents ayant ce rôle ne peuvent accéder qu'aux boîtes de réception, aux rapports et aux conversations. Ils peuvent assigner des conversations à d'autres agents ou eux-mêmes et résoudre des conversations.

Administrateur - Administrateur aura accès à toutes les fonctionnalités de Chatwoot activées pour votre compte, y compris les paramètres, ainsi que tous les privilèges d'un agent normal.

", - "AGENT_TYPES": [ - { - "name": "administrateur", - "label": "Administrateur" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrateur", + "AGENT": "Agent" + }, "LIST": { "404": "Il n'y a aucun agent associé à ce compte", "TITLE": "Gérer les agents de votre équipe", diff --git a/app/javascript/dashboard/i18n/locale/hi/agentMgmt.json b/app/javascript/dashboard/i18n/locale/hi/agentMgmt.json index 633424fed..2b2358c9a 100644 --- a/app/javascript/dashboard/i18n/locale/hi/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/hi/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Add Agent", "LOADING": "Fetching Agent List", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administrator" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, "LIST": { "404": "There are no agents associated to this account", "TITLE": "Manage agents in your team", diff --git a/app/javascript/dashboard/i18n/locale/hu/agentMgmt.json b/app/javascript/dashboard/i18n/locale/hu/agentMgmt.json index 633424fed..2b2358c9a 100644 --- a/app/javascript/dashboard/i18n/locale/hu/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/hu/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Add Agent", "LOADING": "Fetching Agent List", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administrator" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, "LIST": { "404": "There are no agents associated to this account", "TITLE": "Manage agents in your team", diff --git a/app/javascript/dashboard/i18n/locale/it/agentMgmt.json b/app/javascript/dashboard/i18n/locale/it/agentMgmt.json index 77c8bc768..59dfa55fd 100644 --- a/app/javascript/dashboard/i18n/locale/it/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/it/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Aggiungi Agente", "LOADING": "Recupero elenco Operatori", "SIDEBAR_TXT": "

Agenti

Un agente è membro del tuo team di assistenza clienti.

Gli agenti saranno in grado di visualizzare e rispondere ai messaggi dei tuoi utenti. L'elenco mostra tutti gli agenti attualmente presenti nel tuo account.

Clicca su Aggiungi agente per aggiungere un nuovo agente. Ogni agente che aggiungi riceverà un'email con un link di conferma per attivare il loro account, dopo di che possono accedere a Chatwoot e rispondere ai messaggi.

L'accesso alle funzionalità di Chatwoot si basa sui seguenti ruoli.

Agente - Gli agenti con questo ruolo possono accedere solo a messaggi, report e conversazioni. Possono assegnare conversazioni ad altri agenti o a se stessi e risolvere le conversazioni.

Amministratore - L'amministratore avrà accesso a tutte le funzionalità di Chatwoot abilitate per il tuo account, comprese le impostazioni, insieme a tutti i privilegi di un agente normale.

", - "AGENT_TYPES": [ - { - "name": "amministratore", - "label": "Amministratore" - }, - { - "name": "agente", - "label": "Agente" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Amministratore", + "AGENT": "Agente" + }, "LIST": { "404": "Non ci sono agenti associati a questo account", "TITLE": "Gestisci gli agenti nel tuo team", diff --git a/app/javascript/dashboard/i18n/locale/ja/agentMgmt.json b/app/javascript/dashboard/i18n/locale/ja/agentMgmt.json index 633424fed..2b2358c9a 100644 --- a/app/javascript/dashboard/i18n/locale/ja/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ja/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Add Agent", "LOADING": "Fetching Agent List", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administrator" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, "LIST": { "404": "There are no agents associated to this account", "TITLE": "Manage agents in your team", diff --git a/app/javascript/dashboard/i18n/locale/ko/agentMgmt.json b/app/javascript/dashboard/i18n/locale/ko/agentMgmt.json index 633424fed..2b2358c9a 100644 --- a/app/javascript/dashboard/i18n/locale/ko/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ko/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Add Agent", "LOADING": "Fetching Agent List", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administrator" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, "LIST": { "404": "There are no agents associated to this account", "TITLE": "Manage agents in your team", diff --git a/app/javascript/dashboard/i18n/locale/ml/agentMgmt.json b/app/javascript/dashboard/i18n/locale/ml/agentMgmt.json index 9a6d7487e..fff303be2 100644 --- a/app/javascript/dashboard/i18n/locale/ml/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ml/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "ഏജന്റിനെ ചേർക്കുക", "LOADING": "ഏജന്റ് പട്ടിക ലഭ്യമാക്കുന്നു", "SIDEBAR_TXT": "

ഏജന്റുമാർ

നിങ്ങളുടെ ഉപഭോക്തൃ പിന്തുണാ ടീമിലെ ഒരു അംഗമാണ് ഏജൻറ് .

നിങ്ങളുടെ ഉപയോക്താക്കളിൽ നിന്നുള്ള സന്ദേശങ്ങൾ കാണാനും മറുപടി നൽകാനും ഏജന്റുമാർക്ക് കഴിയും. നിലവിൽ നിങ്ങളുടെ accountലുള്ള എല്ലാ ഏജന്റുമാരെയും പട്ടിക കാണിക്കുന്നു.

ഒരു പുതിയ ഏജന്റിനെ ചേർക്കാൻ ഏജന്റിനെ ചേർക്കുക ക്ലിക്കുചെയ്യുക. നിങ്ങൾ ചേർത്ത ഏജന്റിന് അവരുടെ account സജീവമാക്കുന്നതിന് ഒരു സ്ഥിരീകരണ ലിങ്ക് ഉള്ള ഒരു ഇമെയിൽ ലഭിക്കും, അതിനുശേഷം അവർക്ക് Chatwoot ആക്സസ് ചെയ്യാനും സന്ദേശങ്ങളോട് പ്രതികരിക്കാനും കഴിയും.

ചാറ്റ്വൂട്ടിന്റെ സവിശേഷതകളിലേക്കുള്ള ആക്സസ് ഇനിപ്പറയുന്ന റോളുകളെ അടിസ്ഥാനമാക്കിയുള്ളതാണ്.

ഏജൻറ് - ഈ റോൾ ഉള്ള ഏജന്റുമാർക്ക് ഇൻ‌ബോക്സുകൾ‌, റിപ്പോർ‌ട്ടുകൾ‌, സംഭാഷണങ്ങൾ‌ എന്നിവ മാത്രമേ ആക്‌സസ് ചെയ്യാൻ‌ കഴിയൂ. അവർക്ക് മറ്റ് ഏജന്റുമാരുമായോ തങ്ങളുമായോ സംഭാഷണങ്ങൾ നിയോഗിക്കാനും സംഭാഷണങ്ങൾ പരിഹരിക്കാനും കഴിയും. സാധാരണ ഏജന്റുമാരുടെ പ്രത്യേകാവകാശങ്ങൾ.

", - "AGENT_TYPES": [ - { - "name": "അഡ്മിനിസ്‌ട്രേറ്റർ", - "label": "അഡ്മിനിസ്‌ട്രേറ്റർ" - }, - { - "name": "ഏജന്റ്", - "label": "ഏജന്റ്" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "അഡ്മിനിസ്‌ട്രേറ്റർ", + "AGENT": "ഏജന്റ്" + }, "LIST": { "404": "ഈ അക്കൗണ്ടുമായി ബന്ധപ്പെട്ട ഏജന്റുകളൊന്നുമില്ല", "TITLE": "നിങ്ങളുടെ ടീമിലെ ഏജന്റുമാരെ മാനേജുചെയ്യുക", diff --git a/app/javascript/dashboard/i18n/locale/nl/agentMgmt.json b/app/javascript/dashboard/i18n/locale/nl/agentMgmt.json index 2f9fa444e..2ff181b33 100644 --- a/app/javascript/dashboard/i18n/locale/nl/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/nl/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Medewerker toevoegen", "LOADING": "Ophalen van medewerkerslijst", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Beheerder" - }, - { - "name": "agent", - "label": "Medewerker" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Beheerder", + "AGENT": "Medewerker" + }, "LIST": { "404": "Er zijn geen medewerkers gekoppeld aan dit account", "TITLE": "Beheer medewerkers in uw team", diff --git a/app/javascript/dashboard/i18n/locale/pl/agentMgmt.json b/app/javascript/dashboard/i18n/locale/pl/agentMgmt.json index 5604dff14..65e3e7653 100644 --- a/app/javascript/dashboard/i18n/locale/pl/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/pl/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Dodaj agenta", "LOADING": "Pobieranie listy agentów", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administrator" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, "LIST": { "404": "Nie ma agentów powiązanych z tym kontem", "TITLE": "Zarządzaj agentami w zespole", diff --git a/app/javascript/dashboard/i18n/locale/pt/agentMgmt.json b/app/javascript/dashboard/i18n/locale/pt/agentMgmt.json index c2b8209fb..04c4fb160 100644 --- a/app/javascript/dashboard/i18n/locale/pt/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/pt/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Adicionar Agente", "LOADING": "Buscando lista de agente", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administrador" - }, - { - "name": "agent", - "label": "Representante" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrador", + "AGENT": "Representante" + }, "LIST": { "404": "Não há agentes associados a esta conta", "TITLE": "Gerenciar agentes na sua equipe", diff --git a/app/javascript/dashboard/i18n/locale/pt_BR/agentMgmt.json b/app/javascript/dashboard/i18n/locale/pt_BR/agentMgmt.json index 3f668e7ce..d8be4a31a 100644 --- a/app/javascript/dashboard/i18n/locale/pt_BR/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/pt_BR/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Adicionar Agente", "LOADING": "Buscando lista de agente", "SIDEBAR_TXT": "

Agentes

Um Agente é um membro da sua equipe de Suporte ao Cliente.

Os agentes serão capazes de ver e responder as mensagens dos seus usuários. A lista mostra todos os agentes atualmente em sua conta.

Clique em Adicionar Agente para adicionar um novo agente. O agente que você adicionar receberá um e-mail com um link de confirmação para ativar sua conta, para acessar o Chatwoot e responder às mensagens.

O acesso aos recursos do Chatwoot são baseados nas seguintes funções.

Agentes - Agentes com essa função só podem acessar caixas de entrada, relatórios e conversas. Eles podem atribuir conversas a outros agentes ou a eles próprios e resolver conversas.

Administrador - Administrador terá acesso a todos os recursos do Chatwoot ativados para sua conta, incluindo configurações e todos os privilégios de agentes normais.

", - "AGENT_TYPES": [ - { - "name": "administrador", - "label": "Administrador" - }, - { - "name": "agente", - "label": "Agente" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrador", + "AGENT": "Agente" + }, "LIST": { "404": "Não existem agentes associados a esta conta", "TITLE": "Gerenciar agentes da sua equipe", diff --git a/app/javascript/dashboard/i18n/locale/ro/agentMgmt.json b/app/javascript/dashboard/i18n/locale/ro/agentMgmt.json index c1c0721da..bc582e4ed 100644 --- a/app/javascript/dashboard/i18n/locale/ro/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ro/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Adaugă agent", "LOADING": "Se preia lista de agenți", "SIDEBAR_TXT": "

Agenții

Un agent este membru al echipei dumneavoastră de asistență pentru clienți.

Agenți vor putea vizualiza mesajele utilizatorilor și le vor putea răspunde. Lista afișează toți agenții din contul tău.

Faceți clic pe Adăugați Agent pentru a adăuga un agent nou. Agentul pe care îl adăugați va primi un e-mail cu un link de confirmare pentru a-și activa contul, după care pot accesa Chatwoot și răspunde la mesaje.

Accesul la Chatwoot este bazat pe următoarele roluri.

Agent - Agenții cu acest rol pot accesa doar mesaje, rapoarte și conversații. Ei pot atribui conversații altor agenți sau ei înșiși și pot rezolva conversațiile.

Administrator - Administratorul va avea acces la toate caracteristicile Chatwoot activate pentru contul dumneavoastră, inclusiv setările şi facturarea, împreună cu toate privilegiile agenţilor obişnuiţi.

", - "AGENT_TYPES": [ - { - "name": "administrator", - "label": "Administrator" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, "LIST": { "404": "Nu există agenți asociați acestui cont", "TITLE": "Gestionează agenții din echipa ta", diff --git a/app/javascript/dashboard/i18n/locale/ru/agentMgmt.json b/app/javascript/dashboard/i18n/locale/ru/agentMgmt.json index 1f468693e..3f1582d65 100644 --- a/app/javascript/dashboard/i18n/locale/ru/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ru/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Добавить оператора", "LOADING": "Загружаем список операторов", "SIDEBAR_TXT": "

Операторы

Оператор – это член вашей команды поддержки.

Операторы смогут читать сообщения от ваших пользователей и отвечать на них. В этом списке все операторы, подключенные к вашему аккаунту.

Нажмите Добавить оператора, чтобы пригласить нового оператора. Оператор получит письмо со ссылкой для подтверждения аккаунта, после чего он сможет входить в Chatwoot и отвечать на сообщения.

Доступ к функциям Chatwoot определяется следующими ролями:

Оператор - открывает доступ к списку сообщений, отчетам и диалогам. Операторы могут назначать диалоги себе или другим операторам и завершать диалоги.

Администратор - открывает доступ ко всем функциям Chatwook на вашем аккаунте, включая настройки, в дополнение к стандартным правам оператора.

", - "AGENT_TYPES": [ - { - "name": "administrator", - "label": "Администратор" - }, - { - "name": "agent", - "label": "Оператор" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Администратор", + "AGENT": "Оператор" + }, "LIST": { "404": "Нет связанных с этим аккаунтом операторов", "TITLE": "Управляйте операторами вашей команды", diff --git a/app/javascript/dashboard/i18n/locale/sk/agentMgmt.json b/app/javascript/dashboard/i18n/locale/sk/agentMgmt.json index 633424fed..2b2358c9a 100644 --- a/app/javascript/dashboard/i18n/locale/sk/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/sk/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Add Agent", "LOADING": "Fetching Agent List", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administrator" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, "LIST": { "404": "There are no agents associated to this account", "TITLE": "Manage agents in your team", diff --git a/app/javascript/dashboard/i18n/locale/sv/agentMgmt.json b/app/javascript/dashboard/i18n/locale/sv/agentMgmt.json index b82f16c2e..f83187b62 100644 --- a/app/javascript/dashboard/i18n/locale/sv/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/sv/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Lägg till agentur", "LOADING": "Hämtar agentlista", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administratör" - }, - { - "name": "agent", - "label": "Agentkommando" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administratör", + "AGENT": "Agentkommando" + }, "LIST": { "404": "Det finns inga agenter kopplade till detta konto", "TITLE": "Hantera agenter i ditt team", diff --git a/app/javascript/dashboard/i18n/locale/ta/agentMgmt.json b/app/javascript/dashboard/i18n/locale/ta/agentMgmt.json index 838ed4b77..0d46a23cc 100644 --- a/app/javascript/dashboard/i18n/locale/ta/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/ta/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "ஏஜென்ட்களைச் சேர்க்க", "LOADING": "ஏஜென்ட் பட்டியலைப் பெறபடுகிறது", "SIDEBAR_TXT": "

ஏஜென்ட்கள்

ஒரு ஏஜென்ட் உங்கள் வாடிக்கையாளர் சேவை குழுவில் உறுப்பினராக உள்ளார்.

ஏஜென்ட்கள் உங்கள் பயனர்களிடமிருந்து வரும் செய்திகளைக் காணவும் பதிலளிக்கவும் முடியும். தற்போது உங்கள் கணக்கில் உள்ள அனைத்து ஏஜென்ட்களையும் இந்த பட்டியல் காட்டுகிறது.

புதிய ஏஜென்ட்களைச் சேர்க்க முகவரைச் சேர் என்பதைக் கிளிக் செய்யவும். நீங்கள் சேர்க்கும் ஏஜென்ட் தங்கள் கணக்கைச் செயல்படுத்த உறுதிப்படுத்தல் இணைப்பைக் கொண்ட ஈமெயிலைப் பெறுவார், அதன் பிறகு அவர்கள் சாட்வூட்டை அணுகலாம் மற்றும் செய்திகளுக்கு பதிலளிக்கலாம்.

சாட்வூட்டின் அம்சங்களுக்கான அணுகல் பின்வரும் பாத்திரங்களை அடிப்படையாகக் கொண்டது.

ஏஜென்ட் - இந்த பாத்திரத்தைக் கொண்ட ஏஜென்ட்கள் இன்பாக்ஸ்கள், அறிக்கைகள் மற்றும் உரையாடல்களை மட்டுமே அணுக முடியும். அவர்கள் மற்ற ஏஜென்ட்களுக்கோ அல்லது தங்களுக்கோ உரையாடல்களை ஒதுக்கலாம் மற்றும் உரையாடல்களை தீர்க்கலாம்.

நிர்வாகி - அமைப்புகள் உட்பட, உங்கள் கணக்கில் இயக்கப்பட்ட அனைத்து சாட்வூட் அம்சங்களுக்கும் மற்றும் இதர சாதாரண சலுகைகளும் அடங்கும்", - "AGENT_TYPES": [ - { - "name": "administrator", - "label": "நிர்வாகி" - }, - { - "name": "agent", - "label": "ஏஜென்ட்" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "நிர்வாகி", + "AGENT": "ஏஜென்ட்" + }, "LIST": { "404": "இந்த கணக்குடன் எந்த ஏஜென்ட்டும் இல்லை", "TITLE": "உங்கள் அணியில் ஏஜென்ட்களை நிர்வகிக்கவும்", diff --git a/app/javascript/dashboard/i18n/locale/th/agentMgmt.json b/app/javascript/dashboard/i18n/locale/th/agentMgmt.json index 6a7c2a609..bf1d2f43c 100644 --- a/app/javascript/dashboard/i18n/locale/th/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/th/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "เพิ่มพนักงาน", "LOADING": "กำลังดึงข้อมูลรายชื่อพนักงาน", "SIDEBAR_TXT": "

พนักงาน

พนักงาน คือสมาชิกของทีมสนับสนุนลูกค้าของคุณ

พนักงานจะสามารถดูและตอบกลับข้อความจากผู้ใช้ของคุณได้ รายการนี้แสดงพนักงานทั้งหมดที่อยู่ในบัญชีของคุณ

คลิกที่ เพิ่มตัวแทน เพื่อเพิ่มตัวแทนใหม่ ตัวแทนที่คุณเพิ่มจะได้รับอีเมลพร้อมลิงก์ยืนยันเพื่อเปิดใช้งานบัญชีของพวกเขาหลังจากนั้นพวกเขาสามารถเข้าถึง Chatwoot และตอบกลับข้อความได้

การเข้าถึงคุณสมบัติของ Chatwoot ขึ้นอยู่กับบทบาทต่อไปนี้

พนักงาน - ตัวแทนที่มีบทบาทนี้สามารถเข้าถึงได้เฉพาะกล่องจดหมายรายงานและการสนทนา พวกเขาสามารถกำหนดการสนทนาให้กับตัวแทนคนอื่น ๆ หรือตัวเองและแก้ไขการสนทนาได้

ผู้ดูแลระบบ - ผู้ดูแลระบบจะสามารถเข้าถึงคุณลักษณะ Chatwoot ทั้งหมดที่เปิดใช้งานสำหรับบัญชีของคุณรวมถึงการตั้งค่าพร้อมด้วย สิทธิพิเศษของตัวแทนทั่วไป

", - "AGENT_TYPES": [ - { - "name": "administrator", - "label": "ผู้ดูเเล" - }, - { - "name": "agent", - "label": "พนักงาน" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "ผู้ดูเเล", + "AGENT": "พนักงาน" + }, "LIST": { "404": "ไม่มีพนักงานที่เกี่ยวข้องในบัญชีนี้", "TITLE": "จัดการพนักงานในทีมของคุณ", diff --git a/app/javascript/dashboard/i18n/locale/tr/agentMgmt.json b/app/javascript/dashboard/i18n/locale/tr/agentMgmt.json index 633424fed..2b2358c9a 100644 --- a/app/javascript/dashboard/i18n/locale/tr/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/tr/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Add Agent", "LOADING": "Fetching Agent List", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administrator" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, "LIST": { "404": "There are no agents associated to this account", "TITLE": "Manage agents in your team", diff --git a/app/javascript/dashboard/i18n/locale/uk/agentMgmt.json b/app/javascript/dashboard/i18n/locale/uk/agentMgmt.json index 94277682c..2578e4cb3 100644 --- a/app/javascript/dashboard/i18n/locale/uk/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/uk/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Додати агента", "LOADING": "Отримання списку агентів", "SIDEBAR_TXT": "

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": [ - { - "name": "адміністратор", - "label": "Адміністратор" - }, - { - "name": "агент", - "label": "Агент" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Адміністратор", + "AGENT": "Агент" + }, "LIST": { "404": "Немає агентів, пов'язаних з цим обліковим записом", "TITLE": "Керування агентами у вашій команді", diff --git a/app/javascript/dashboard/i18n/locale/vi/agentMgmt.json b/app/javascript/dashboard/i18n/locale/vi/agentMgmt.json index 633424fed..2b2358c9a 100644 --- a/app/javascript/dashboard/i18n/locale/vi/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/vi/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Add Agent", "LOADING": "Fetching Agent List", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administrator" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, "LIST": { "404": "There are no agents associated to this account", "TITLE": "Manage agents in your team", diff --git a/app/javascript/dashboard/i18n/locale/zh/agentMgmt.json b/app/javascript/dashboard/i18n/locale/zh/agentMgmt.json index 633424fed..2b2358c9a 100644 --- a/app/javascript/dashboard/i18n/locale/zh/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/zh/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Add Agent", "LOADING": "Fetching Agent List", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administrator" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, "LIST": { "404": "There are no agents associated to this account", "TITLE": "Manage agents in your team", diff --git a/app/javascript/dashboard/i18n/locale/zh_CN/agentMgmt.json b/app/javascript/dashboard/i18n/locale/zh_CN/agentMgmt.json index fd27e292c..8b1116e4b 100644 --- a/app/javascript/dashboard/i18n/locale/zh_CN/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/zh_CN/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "添加客服代理", "LOADING": "正在获取客服代理列表", "SIDEBAR_TXT": "

客服代理

客服代理 是您的客户支持团队成员。

客服 代理人将能够查看和回复您用户的消息。 列表显示当前帐户中的所有客服代理人。

点击 添加客服代理 添加新客服代理人。 您添加的客服代理将收到一封含有确认链接的电子邮件来激活他们的帐户,然后他们可以访问Chatwoot 并回复消息。

聊天窗口的功能基于以下角色。

Agent - 具有此角色的代理只能访问收件箱、报告和会话。 他们可以将对话分配给其他客服代理人或自己,并解决会话。

管理员 - 管理员将可以访问您账户中所有已启用的聊天功能。 包括设置,以及所有正常客服代理的权限。

", - "AGENT_TYPES": [ - { - "name": "administrator", - "label": "管理员" - }, - { - "name": "agent", - "label": "客服代理" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "管理员", + "AGENT": "客服代理" + }, "LIST": { "404": "没有与此账户关联的代理", "TITLE": "管理您团队中的代理", diff --git a/app/javascript/dashboard/i18n/locale/zh_TW/agentMgmt.json b/app/javascript/dashboard/i18n/locale/zh_TW/agentMgmt.json index 633424fed..2b2358c9a 100644 --- a/app/javascript/dashboard/i18n/locale/zh_TW/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/zh_TW/agentMgmt.json @@ -4,16 +4,10 @@ "HEADER_BTN_TXT": "Add Agent", "LOADING": "Fetching Agent List", "SIDEBAR_TXT": "

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": [ - { - "name": "administrator", - "label": "Administrator" - }, - { - "name": "agent", - "label": "Agent" - } - ], + "AGENT_TYPES": { + "ADMINISTRATOR": "Administrator", + "AGENT": "Agent" + }, "LIST": { "404": "There are no agents associated to this account", "TITLE": "Manage agents in your team", diff --git a/app/javascript/dashboard/routes/dashboard/settings/agents/AddAgent.vue b/app/javascript/dashboard/routes/dashboard/settings/agents/AddAgent.vue index 4c5b571e7..8cc1a83e1 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/agents/AddAgent.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/agents/AddAgent.vue @@ -21,16 +21,11 @@
+
+ +
+
+

@@ -157,6 +161,7 @@ import { mixin as clickaway } from 'vue-clickaway'; import adminMixin from '../../mixins/isAdmin'; import Auth from '../../api/auth'; import SidebarItem from './SidebarItem'; +import AvailabilityStatus from './AvailabilityStatus'; import { frontendURL } from '../../helper/URLHelper'; import Thumbnail from '../widgets/Thumbnail'; import { getSidebarItems } from '../../i18n/default-sidebar'; @@ -167,6 +172,7 @@ export default { components: { SidebarItem, Thumbnail, + AvailabilityStatus, }, mixins: [clickaway, adminMixin, alertMixin], props: { diff --git a/app/javascript/dashboard/components/layout/specs/AvailabilityStatus.spec.js b/app/javascript/dashboard/components/layout/specs/AvailabilityStatus.spec.js new file mode 100644 index 000000000..53a11406b --- /dev/null +++ b/app/javascript/dashboard/components/layout/specs/AvailabilityStatus.spec.js @@ -0,0 +1,77 @@ +import AvailabilityStatus from '../AvailabilityStatus'; +import { createLocalVue, mount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import VueI18n from 'vue-i18n'; + +import i18n from 'dashboard/i18n'; + +const localVue = createLocalVue(); +localVue.use(Vuex); +localVue.use(VueI18n); +localVue.locale('en', i18n.en); + +describe('AvailabilityStatus', () => { + const currentUser = { availability_status: 'online' }; + let store = null; + let actions = null; + let modules = null; + let availabilityStatus = null; + + beforeEach(() => { + actions = { + updateAvailability: jest.fn(() => { + return Promise.resolve(); + }), + }; + + modules = { + auth: { + getters: { + getCurrentUser: () => currentUser, + }, + }, + }; + + store = new Vuex.Store({ + actions, + modules, + }); + + availabilityStatus = mount(AvailabilityStatus, { + store, + localVue, + }); + }); + + it('shows current user status', () => { + const statusViewTitle = availabilityStatus.find('.status-view--title'); + + expect(statusViewTitle.text()).toBe(currentUser.availability_status); + }); + + it('opens the menu when user clicks "change"', async () => { + expect(availabilityStatus.find('.dropdown-pane').exists()).toBe(false); + + await availabilityStatus + .find('.status-change--change-button') + .trigger('click'); + + expect(availabilityStatus.find('.dropdown-pane').exists()).toBe(true); + }); + + it('dispatches an action when user changes status', async () => { + await availabilityStatus + .find('.status-change--change-button') + .trigger('click'); + + await availabilityStatus + .find('.status-change li:last-child button') + .trigger('click'); + + expect(actions.updateAvailability).toBeCalledWith( + expect.any(Object), + { availability: 'offline' }, + undefined + ); + }); +}); diff --git a/app/javascript/dashboard/i18n/locale/ar/settings.json b/app/javascript/dashboard/i18n/locale/ar/settings.json index 9046959a5..1d0d04288 100644 --- a/app/javascript/dashboard/i18n/locale/ar/settings.json +++ b/app/javascript/dashboard/i18n/locale/ar/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "يتغيرون", "CHANGE_ACCOUNTS": "تبديل الحساب", "SELECTOR_SUBTITLE": "اختر حساباً من القائمة التالية", "PROFILE_SETTINGS": "إعدادات الملف الشخصي", diff --git a/app/javascript/dashboard/i18n/locale/ca/settings.json b/app/javascript/dashboard/i18n/locale/ca/settings.json index 35755f8af..f080e7b31 100644 --- a/app/javascript/dashboard/i18n/locale/ca/settings.json +++ b/app/javascript/dashboard/i18n/locale/ca/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Canviar", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Configuració del Perfil", diff --git a/app/javascript/dashboard/i18n/locale/cs/settings.json b/app/javascript/dashboard/i18n/locale/cs/settings.json index 086a978b5..cb31c1e08 100644 --- a/app/javascript/dashboard/i18n/locale/cs/settings.json +++ b/app/javascript/dashboard/i18n/locale/cs/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Změnit", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Nastavení profilu", diff --git a/app/javascript/dashboard/i18n/locale/da/settings.json b/app/javascript/dashboard/i18n/locale/da/settings.json index 7fc2e8519..f300f5fa3 100644 --- a/app/javascript/dashboard/i18n/locale/da/settings.json +++ b/app/javascript/dashboard/i18n/locale/da/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Change", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Profile Settings", diff --git a/app/javascript/dashboard/i18n/locale/de/settings.json b/app/javascript/dashboard/i18n/locale/de/settings.json index 641425036..b03b8ec13 100644 --- a/app/javascript/dashboard/i18n/locale/de/settings.json +++ b/app/javascript/dashboard/i18n/locale/de/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Wechseln", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Profileinstellungen", diff --git a/app/javascript/dashboard/i18n/locale/el/settings.json b/app/javascript/dashboard/i18n/locale/el/settings.json index adfc33d29..7458348cd 100644 --- a/app/javascript/dashboard/i18n/locale/el/settings.json +++ b/app/javascript/dashboard/i18n/locale/el/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Να αλλάξει", "CHANGE_ACCOUNTS": "Αλλαγή Λογαριασμού", "SELECTOR_SUBTITLE": "Επιλέξτε ένα λογαριασμό από την Λίστα", "PROFILE_SETTINGS": "Ρυθμίσεις Προφίλ", diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json index 0ac7e8d61..3e96b9861 100644 --- a/app/javascript/dashboard/i18n/locale/en/settings.json +++ b/app/javascript/dashboard/i18n/locale/en/settings.json @@ -90,6 +90,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Change", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Profile Settings", diff --git a/app/javascript/dashboard/i18n/locale/es/settings.json b/app/javascript/dashboard/i18n/locale/es/settings.json index cf70b7518..99f6e38fd 100644 --- a/app/javascript/dashboard/i18n/locale/es/settings.json +++ b/app/javascript/dashboard/i18n/locale/es/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Cambiar", "CHANGE_ACCOUNTS": "Cambiar de cuenta", "SELECTOR_SUBTITLE": "Seleccione una cuenta de la siguiente lista", "PROFILE_SETTINGS": "Ajustes del perfil", diff --git a/app/javascript/dashboard/i18n/locale/fa/settings.json b/app/javascript/dashboard/i18n/locale/fa/settings.json index 15de1cf3d..99d8efab4 100644 --- a/app/javascript/dashboard/i18n/locale/fa/settings.json +++ b/app/javascript/dashboard/i18n/locale/fa/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "عوض شدن", "CHANGE_ACCOUNTS": "سوییچ به یک حساب دیگر", "SELECTOR_SUBTITLE": "از لیست یکی از حساب‌ها را انتخاب کنید", "PROFILE_SETTINGS": "تنظیمات پروفایل", diff --git a/app/javascript/dashboard/i18n/locale/fi/settings.json b/app/javascript/dashboard/i18n/locale/fi/settings.json index 7fc2e8519..f300f5fa3 100644 --- a/app/javascript/dashboard/i18n/locale/fi/settings.json +++ b/app/javascript/dashboard/i18n/locale/fi/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Change", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Profile Settings", diff --git a/app/javascript/dashboard/i18n/locale/fr/settings.json b/app/javascript/dashboard/i18n/locale/fr/settings.json index e4842fe05..147629e4f 100644 --- a/app/javascript/dashboard/i18n/locale/fr/settings.json +++ b/app/javascript/dashboard/i18n/locale/fr/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Changer", "CHANGE_ACCOUNTS": "Changer de compte", "SELECTOR_SUBTITLE": "Sélectionnez un compte dans la liste suivante", "PROFILE_SETTINGS": "Paramètres de profil", diff --git a/app/javascript/dashboard/i18n/locale/hi/settings.json b/app/javascript/dashboard/i18n/locale/hi/settings.json index 7fc2e8519..f300f5fa3 100644 --- a/app/javascript/dashboard/i18n/locale/hi/settings.json +++ b/app/javascript/dashboard/i18n/locale/hi/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Change", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Profile Settings", diff --git a/app/javascript/dashboard/i18n/locale/hu/settings.json b/app/javascript/dashboard/i18n/locale/hu/settings.json index 7fc2e8519..f300f5fa3 100644 --- a/app/javascript/dashboard/i18n/locale/hu/settings.json +++ b/app/javascript/dashboard/i18n/locale/hu/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Change", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Profile Settings", diff --git a/app/javascript/dashboard/i18n/locale/it/settings.json b/app/javascript/dashboard/i18n/locale/it/settings.json index a3a892508..289bd79a0 100644 --- a/app/javascript/dashboard/i18n/locale/it/settings.json +++ b/app/javascript/dashboard/i18n/locale/it/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Cambia", "CHANGE_ACCOUNTS": "Cambia Profilo/Account", "SELECTOR_SUBTITLE": "Seleziona un account dal seguente elenco", "PROFILE_SETTINGS": "Impostazioni profilo", diff --git a/app/javascript/dashboard/i18n/locale/ja/settings.json b/app/javascript/dashboard/i18n/locale/ja/settings.json index 7fc2e8519..f300f5fa3 100644 --- a/app/javascript/dashboard/i18n/locale/ja/settings.json +++ b/app/javascript/dashboard/i18n/locale/ja/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Change", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Profile Settings", diff --git a/app/javascript/dashboard/i18n/locale/ko/settings.json b/app/javascript/dashboard/i18n/locale/ko/settings.json index 7fc2e8519..f300f5fa3 100644 --- a/app/javascript/dashboard/i18n/locale/ko/settings.json +++ b/app/javascript/dashboard/i18n/locale/ko/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Change", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Profile Settings", diff --git a/app/javascript/dashboard/i18n/locale/ml/settings.json b/app/javascript/dashboard/i18n/locale/ml/settings.json index 5598623e5..2db22a48a 100644 --- a/app/javascript/dashboard/i18n/locale/ml/settings.json +++ b/app/javascript/dashboard/i18n/locale/ml/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "മാറ്റം വരുത്തുക", "CHANGE_ACCOUNTS": "അക്കൗണ്ട് സ്വിച്ചുചെയ്യുക", "SELECTOR_SUBTITLE": "ഇനിപ്പറയുന്ന ലിസ്റ്റിൽ നിന്ന് ഒരു അക്കൗണ്ട് തിരഞ്ഞെടുക്കുക", "PROFILE_SETTINGS": "പ്രൊഫൈൽ ക്രമീകരണങ്ങൾ", diff --git a/app/javascript/dashboard/i18n/locale/nl/settings.json b/app/javascript/dashboard/i18n/locale/nl/settings.json index fb16bc6f6..2ae98e3b3 100644 --- a/app/javascript/dashboard/i18n/locale/nl/settings.json +++ b/app/javascript/dashboard/i18n/locale/nl/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Veranderen", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Profiel instellingen", diff --git a/app/javascript/dashboard/i18n/locale/pl/settings.json b/app/javascript/dashboard/i18n/locale/pl/settings.json index 69cb9ebc0..1693e37d4 100644 --- a/app/javascript/dashboard/i18n/locale/pl/settings.json +++ b/app/javascript/dashboard/i18n/locale/pl/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Zmienić", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Ustawienia profilu", diff --git a/app/javascript/dashboard/i18n/locale/pt/settings.json b/app/javascript/dashboard/i18n/locale/pt/settings.json index ffb1e57ba..ae7be9ae0 100644 --- a/app/javascript/dashboard/i18n/locale/pt/settings.json +++ b/app/javascript/dashboard/i18n/locale/pt/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Trocar", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Configurações do perfil", diff --git a/app/javascript/dashboard/i18n/locale/pt_BR/settings.json b/app/javascript/dashboard/i18n/locale/pt_BR/settings.json index 0f6f21414..fd322753a 100644 --- a/app/javascript/dashboard/i18n/locale/pt_BR/settings.json +++ b/app/javascript/dashboard/i18n/locale/pt_BR/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Trocar", "CHANGE_ACCOUNTS": "Trocar Conta", "SELECTOR_SUBTITLE": "Selecione uma conta da lista a seguir", "PROFILE_SETTINGS": "Configurações do Perfil", diff --git a/app/javascript/dashboard/i18n/locale/ro/settings.json b/app/javascript/dashboard/i18n/locale/ro/settings.json index f4720942f..c35110a7a 100644 --- a/app/javascript/dashboard/i18n/locale/ro/settings.json +++ b/app/javascript/dashboard/i18n/locale/ro/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Schimba", "CHANGE_ACCOUNTS": "Comută contul", "SELECTOR_SUBTITLE": "Selectaţi un cont din următoarea listă", "PROFILE_SETTINGS": "Setări profil", diff --git a/app/javascript/dashboard/i18n/locale/ru/settings.json b/app/javascript/dashboard/i18n/locale/ru/settings.json index 1531748a4..ca64801e3 100644 --- a/app/javascript/dashboard/i18n/locale/ru/settings.json +++ b/app/javascript/dashboard/i18n/locale/ru/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Изменить", "CHANGE_ACCOUNTS": "Сменить Аккаунт", "SELECTOR_SUBTITLE": "Выберите аккаунт из списка", "PROFILE_SETTINGS": "Настройки профиля", diff --git a/app/javascript/dashboard/i18n/locale/sk/settings.json b/app/javascript/dashboard/i18n/locale/sk/settings.json index 7fc2e8519..f300f5fa3 100644 --- a/app/javascript/dashboard/i18n/locale/sk/settings.json +++ b/app/javascript/dashboard/i18n/locale/sk/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Change", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Profile Settings", diff --git a/app/javascript/dashboard/i18n/locale/sv/settings.json b/app/javascript/dashboard/i18n/locale/sv/settings.json index b8f1443c4..23a0908c4 100644 --- a/app/javascript/dashboard/i18n/locale/sv/settings.json +++ b/app/javascript/dashboard/i18n/locale/sv/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Att förändra", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Profilens inställningar", diff --git a/app/javascript/dashboard/i18n/locale/ta/settings.json b/app/javascript/dashboard/i18n/locale/ta/settings.json index ede80323d..b208da409 100644 --- a/app/javascript/dashboard/i18n/locale/ta/settings.json +++ b/app/javascript/dashboard/i18n/locale/ta/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "மாற்ற", "CHANGE_ACCOUNTS": "கணக்கை மாற்றவும்", "SELECTOR_SUBTITLE": "பின்வரும் பட்டியலிலிருந்து ஒரு கணக்கைத் தேர்ந்தெடுக்கவும்", "PROFILE_SETTINGS": "சுயவிவர அமைப்புகள்", diff --git a/app/javascript/dashboard/i18n/locale/th/settings.json b/app/javascript/dashboard/i18n/locale/th/settings.json index d5b9aa454..74dba3a40 100644 --- a/app/javascript/dashboard/i18n/locale/th/settings.json +++ b/app/javascript/dashboard/i18n/locale/th/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Change", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Profile Settings", diff --git a/app/javascript/dashboard/i18n/locale/tr/settings.json b/app/javascript/dashboard/i18n/locale/tr/settings.json index 7fc2e8519..f300f5fa3 100644 --- a/app/javascript/dashboard/i18n/locale/tr/settings.json +++ b/app/javascript/dashboard/i18n/locale/tr/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Change", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Profile Settings", diff --git a/app/javascript/dashboard/i18n/locale/uk/settings.json b/app/javascript/dashboard/i18n/locale/uk/settings.json index 3935fb145..f7ce7d81b 100644 --- a/app/javascript/dashboard/i18n/locale/uk/settings.json +++ b/app/javascript/dashboard/i18n/locale/uk/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Змінити", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Налаштування облікового запису", diff --git a/app/javascript/dashboard/i18n/locale/vi/settings.json b/app/javascript/dashboard/i18n/locale/vi/settings.json index 7fc2e8519..f300f5fa3 100644 --- a/app/javascript/dashboard/i18n/locale/vi/settings.json +++ b/app/javascript/dashboard/i18n/locale/vi/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "Change", "CHANGE_ACCOUNTS": "Switch Account", "SELECTOR_SUBTITLE": "Select an account from the following list", "PROFILE_SETTINGS": "Profile Settings", diff --git a/app/javascript/dashboard/i18n/locale/zh/settings.json b/app/javascript/dashboard/i18n/locale/zh/settings.json index b89ce4537..3d998f821 100644 --- a/app/javascript/dashboard/i18n/locale/zh/settings.json +++ b/app/javascript/dashboard/i18n/locale/zh/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "改變", "CHANGE_ACCOUNTS": "切换账户", "SELECTOR_SUBTITLE": "从以下列表中选择一个账户", "PROFILE_SETTINGS": "个人资料设置", diff --git a/app/javascript/dashboard/i18n/locale/zh_CN/settings.json b/app/javascript/dashboard/i18n/locale/zh_CN/settings.json index b89ce4537..3d998f821 100644 --- a/app/javascript/dashboard/i18n/locale/zh_CN/settings.json +++ b/app/javascript/dashboard/i18n/locale/zh_CN/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "改變", "CHANGE_ACCOUNTS": "切换账户", "SELECTOR_SUBTITLE": "从以下列表中选择一个账户", "PROFILE_SETTINGS": "个人资料设置", diff --git a/app/javascript/dashboard/i18n/locale/zh_TW/settings.json b/app/javascript/dashboard/i18n/locale/zh_TW/settings.json index 3d3ce6134..41e23efdc 100644 --- a/app/javascript/dashboard/i18n/locale/zh_TW/settings.json +++ b/app/javascript/dashboard/i18n/locale/zh_TW/settings.json @@ -88,6 +88,7 @@ } }, "SIDEBAR_ITEMS": { + "CHANGE_AVAILABILITY_STATUS": "改變", "CHANGE_ACCOUNTS": "切換帳戶", "SELECTOR_SUBTITLE": "從以下列表中選擇一個帳戶", "PROFILE_SETTINGS": "個人資料設定", diff --git a/app/javascript/dashboard/store/modules/auth.js b/app/javascript/dashboard/store/modules/auth.js index ba26ea79a..8251046fc 100644 --- a/app/javascript/dashboard/store/modules/auth.js +++ b/app/javascript/dashboard/store/modules/auth.js @@ -105,6 +105,13 @@ export const actions = { } }, + updateAvailability: ({ commit }, { availability }) => { + authAPI.updateAvailability({ availability }).then(response => { + setUser(response.data, getHeaderExpiry(response)); + commit(types.default.SET_CURRENT_USER); + }); + }, + setCurrentAccountId({ commit }, accountId) { commit(types.default.SET_CURRENT_ACCOUNT_ID, accountId); }, From b81eef6ffd021664d1bc320903d5a301a86cad59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B1=20=C9=91=C9=B7=20=C9=96=CE=B5=C9=BE=CE=B5=C6=99?= Date: Fri, 2 Oct 2020 12:58:15 +0700 Subject: [PATCH 24/57] chore: Add translation for Vietnamese (#1292) Co-authored-by: Pranav Raj S --- config/locales/devise.vi.yml | 84 ++++++++++++++++++------------------ config/locales/vi.yml | 30 ++++++------- 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/config/locales/devise.vi.yml b/config/locales/devise.vi.yml index 9fdffdbc0..b2e3adf2d 100644 --- a/config/locales/devise.vi.yml +++ b/config/locales/devise.vi.yml @@ -2,59 +2,59 @@ vi: devise: confirmations: - confirmed: "Your email address has been successfully confirmed." - send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." - send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." + confirmed: "Email của bạn đã được xác nhận thành công." + send_instructions: "Bạn sẽ nhận được một email có hướng dẫn về cách xác nhận địa chỉ email của bạn sau vài phút." + send_paranoid_instructions: "Nếu địa chỉ email của bạn tồn tại trong cơ sở dữ liệu của chúng tôi, bạn sẽ nhận được một email có hướng dẫn cách xác nhận địa chỉ email của bạn trong vài phút." failure: - already_authenticated: "You are already signed in." - inactive: "Your account is not activated yet." - invalid: "Invalid %{authentication_keys}/password or account is not verified yet." - locked: "Your account is locked." - last_attempt: "You have one more attempt before your account is locked." - not_found_in_database: "Invalid %{authentication_keys} or password." - timeout: "Your session expired. Please sign in again to continue." - unauthenticated: "You need to sign in or sign up before continuing." - unconfirmed: "You have to confirm your email address before continuing." + already_authenticated: "Bạn đã đăng nhập." + inactive: Tài khoản của bạn chưa được kích hoạt. + invalid: "%{authentication_keys}/mật khẩu không hợp lệ hoặc tài khoản chưa được xác thực." + locked: "Tài khoản của bạn đã bị khoá." + last_attempt: "Bạn còn một lần thử nữa trước khi tài khoản của bạn bị khóa." + not_found_in_database: "%{authentication_keys} hoặc mật khẩu không hợp lệ." + timeout: "Phiên của bạn đã hết hạn. Vui lòng đăng nhập lại để tiếp tục." + unauthenticated: "Bạn cần đăng nhập hoặc đăng ký trước khi tiếp tục." + unconfirmed: "Bạn phải xác nhận địa chỉ email của mình trước khi tiếp tục." mailer: confirmation_instructions: - subject: "Confirmation Instructions" + subject: "Hướng Dẫn Xác Nhận" reset_password_instructions: - subject: "Reset password instructions" + subject: "Hướng dẫn thay đổi mật khẩu" unlock_instructions: - subject: "Unlock instructions" + subject: "Hướng dẫn mở khoá" password_change: - subject: "Password Changed" + subject: "Mật khẩu đã được thay đổi" omniauth_callbacks: - failure: "Could not authenticate you from %{kind} because \"%{reason}\"." - success: "Successfully authenticated from %{kind} account." + failure: "Không thể xác thực bạn từ %{kind} vì \"%{reason}\"." + success: "Xác thực thành công từ %{kind} tài khoản." passwords: - no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." - send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." - send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." - updated: "Your password has been changed successfully. You are now signed in." - updated_not_active: "Your password has been changed successfully." + no_token: "Bạn không thể truy cập trang này nếu không nhận được email đặt lại mật khẩu. Nếu bạn đến từ một email đặt lại mật khẩu, hãy đảm bảo rằng bạn đã sử dụng URL đầy đủ được cung cấp." + send_instructions: "Bạn sẽ nhận được email hướng dẫn cách đặt lại mật khẩu sau vài phút." + send_paranoid_instructions: "Nếu địa chỉ email của bạn tồn tại trong cơ sở dữ liệu của chúng tôi, bạn sẽ nhận được liên kết khôi phục mật khẩu tại địa chỉ email của mình sau vài phút." + updated: "Mật khẩu của bạn đã được thay đổi thành công. Bây giờ bạn đã đăng nhập." + updated_not_active: "Mật khẩu của bạn đã được thay đổi thành công." registrations: - destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." - signed_up: "Welcome! You have signed up successfully." - signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." - signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." - signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." - update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." - updated: "Your account has been updated successfully." + destroyed: "Tạm biệt! Tài khoản của bạn đã được hủy thành công. Mong rằng chúng tôi sẽ sớm gặp lại bạn." + signed_up: "Chào mừng! Bạn đã đăng ký thành công." + signed_up_but_inactive: "Bạn đã đăng ký thành công. Tuy nhiên, chúng tôi không thể đăng nhập cho bạn vì tài khoản của bạn chưa được kích hoạt." + signed_up_but_locked: "Bạn đã đăng ký thành công. Tuy nhiên, chúng tôi không thể đăng nhập cho bạn vì tài khoản của bạn bị khóa." + signed_up_but_unconfirmed: "Một tin nhắn với một liên kết xác nhận đã được gửi đến địa chỉ email của bạn. Vui lòng nhấp vào liên kết để kích hoạt tài khoản của bạn." + update_needs_confirmation: "Bạn đã cập nhật tài khoản của mình thành công, nhưng chúng tôi cần xác minh địa chỉ email mới của bạn. Vui lòng kiểm tra email của bạn và nhấp vào liên kết xác nhận để xác nhận địa chỉ email mới của bạn." + updated: "Tài khoản của bạn đã được cập nhật thành công." sessions: - signed_in: "Signed in successfully." - signed_out: "Signed out successfully." - already_signed_out: "Signed out successfully." + signed_in: "Đã đăng nhập thành công." + signed_out: "Đã đăng xuất thành công." + already_signed_out: "Đã đăng xuất thành công." unlocks: - send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." - send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." - unlocked: "Your account has been unlocked successfully. Please sign in to continue." + send_instructions: "Bạn sẽ nhận được một email có hướng dẫn về cách mở khóa tài khoản của mình sau vài phút." + send_paranoid_instructions: "Nếu tài khoản của bạn tồn tại, bạn sẽ nhận được email hướng dẫn cách mở khóa tài khoản sau vài phút." + unlocked: "Tài khoản của bạn đã được mở khóa thành công. Vui lòng đăng nhập để tiếp tục." errors: messages: - already_confirmed: "was already confirmed, please try signing in" - confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" - expired: "has expired, please request a new one" - not_found: "not found" - not_locked: "was not locked" + already_confirmed: "đã được xác nhận, vui lòng thử đăng nhập" + confirmation_period_expired: "cần được xác nhận trong %{period}, vui lòng yêu cầu một cái mới" + expired: "đã hết hạn, vui lòng yêu cầu một cái mới" + not_found: "không tìm thấy" + not_locked: "không được khoá" not_saved: - other: "%{count} errors prohibited this %{resource} from being saved:" + other: "Có %{count} lỗi được tìm thấy từ %{resource}:" diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 9f233a00b..a79a94f53 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -19,26 +19,26 @@ vi: hello: "Hello world" messages: - reset_password_success: Woot! Request for password reset is successful. Check your mail for instructions. - reset_password_failure: Uh ho! We could not find any user with the specified email. + reset_password_success: Chà! Yêu cầu đặt lại mật khẩu thành công. Kiểm tra thư của bạn để biết hướng dẫn. + reset_password_failure: Uh ho! Chúng tôi không thể tìm thấy bất kỳ người dùng nào có email được chỉ định. errors: signup: - disposable_email: We do not allow disposable emails - invalid_email: You have entered an invalid email - email_already_exists: "You have already signed up for an account with %{email}" - failed: Signup failed + disposable_email: Chúng tôi không cho phép các email dùng một lần + invalid_email: Bạn đã nhập một email không hợp lệ + email_already_exists: "Bạn đã đăng ký một tài khoản với %{email}" + failed: Đăng ký thât bại conversations: activity: status: - resolved: "Conversation was marked resolved by %{user_name}" - open: "Conversation was reopened by %{user_name}" + resolved: "Cuộc trò chuyện được đánh dấu là đã giải quyết bởi %{user_name}" + open: "Cuộc trò chuyện đã được mở lại bởi %{user_name}" assignee: - assigned: "Assigned to %{assignee_name} by %{user_name}" - removed: "Conversation unassigned by %{user_name}" + assigned: "Chỉ định %{assignee_name} bởi %{user_name}" + removed: "Cuộc hội thoại chưa được chỉ định bởi %{user_name}" templates: - greeting_message_body: "%{account_name} typically replies in a few hours." - ways_to_reach_you_message_body: "Give the team a way to reach you." - email_input_box_message_body: "Get notified by email" + greeting_message_body: "%{account_name} thường trả lời trong vài giờ." + ways_to_reach_you_message_body: "Cung cấp cho nhóm một cách để tiếp cận bạn." + email_input_box_message_body: "Nhận thông báo qua email" reply: - email_subject: "New messages on this conversation" - transcript_subject: "Conversation Transcript" + email_subject: "Tin nhắn mới về cuộc trò chuyện này" + transcript_subject: "Bản ghi cuộc hội thoại" From 98cb09104e0c8b95e82b3a365d2113b02405d4da Mon Sep 17 00:00:00 2001 From: Dmitriy Shcherbakan Date: Fri, 2 Oct 2020 12:57:26 +0300 Subject: [PATCH 25/57] chore: Refactor `DeviseOverrides::ConfirmationsController#create` (#1297) --- .../confirmations_controller.rb | 32 +++++++---- .../devise/confirmations_controller_spec.rb | 56 +++++++++++++++++++ 2 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 spec/controllers/devise/confirmations_controller_spec.rb diff --git a/app/controllers/devise_overrides/confirmations_controller.rb b/app/controllers/devise_overrides/confirmations_controller.rb index 057e77b12..0cb71f0d9 100644 --- a/app/controllers/devise_overrides/confirmations_controller.rb +++ b/app/controllers/devise_overrides/confirmations_controller.rb @@ -4,22 +4,34 @@ class DeviseOverrides::ConfirmationsController < Devise::ConfirmationsController def create @confirmable = User.find_by(confirmation_token: params[:confirmation_token]) - if @confirmable - if @confirmable.confirm || (@confirmable.confirmed_at && @confirmable.reset_password_token) - # confirmed now or already confirmed but quit before setting a password - render json: { "message": 'Success', "redirect_url": create_reset_token_link(@confirmable) }, status: :ok - elsif @confirmable.confirmed_at - render json: { "message": 'Already confirmed', "redirect_url": '/' }, status: 422 - else - render json: { "message": 'Failure', "redirect_url": '/' }, status: 422 - end + + if confirm + render_confirmation_success else - render json: { "message": 'Invalid token', "redirect_url": '/' }, status: 422 + render_confirmation_error end end protected + def confirm + @confirmable&.confirm || (@confirmable&.confirmed_at && @confirmable&.reset_password_token) + end + + def render_confirmation_success + render json: { "message": 'Success', "redirect_url": create_reset_token_link(@confirmable) }, status: :ok + end + + def render_confirmation_error + if @confirmable.blank? + render json: { "message": 'Invalid token', "redirect_url": '/' }, status: 422 + elsif @confirmable.confirmed_at + render json: { "message": 'Already confirmed', "redirect_url": '/' }, status: 422 + else + render json: { "message": 'Failure', "redirect_url": '/' }, status: 422 + end + end + def create_reset_token_link(user) raw, enc = Devise.token_generator.generate(user.class, :reset_password_token) user.reset_password_token = enc diff --git a/spec/controllers/devise/confirmations_controller_spec.rb b/spec/controllers/devise/confirmations_controller_spec.rb new file mode 100644 index 000000000..6970b5777 --- /dev/null +++ b/spec/controllers/devise/confirmations_controller_spec.rb @@ -0,0 +1,56 @@ +require 'rails_helper' + +RSpec.describe 'Token Confirmation', type: :request do + describe 'POST /auth/confirmation' do + let(:response_json) { JSON.parse(response.body, symbolize_names: true) } + + before do + create(:user, email: 'john.doe@gmail.com', **user_attributes) + + post user_confirmation_url, params: { confirmation_token: confirmation_token } + end + + context 'when token is valid' do + let(:user_attributes) { { confirmation_token: '12345', skip_confirmation: false } } + let(:confirmation_token) { '12345' } + + it 'has status 200' do + expect(response.status).to eq 200 + end + + it 'returns message "Success"' do + expect(response_json[:message]).to eq 'Success' + end + + it 'returns "redirect_url"' do + expect(response_json[:redirect_url]).to include '/app/auth/password/edit?config=default&redirect_url=&reset_password_token' + end + end + + context 'when token is invalid' do + let(:user_attributes) { { confirmation_token: '12345' } } + let(:confirmation_token) { '' } + + it 'has status 422' do + expect(response.status).to eq 422 + end + + it 'returns message "Invalid token"' do + expect(response_json).to eq({ message: 'Invalid token', redirect_url: '/' }) + end + end + + context 'when user had already been confirmed' do + let(:user_attributes) { { confirmation_token: '12345' } } + let(:confirmation_token) { '12345' } + + it 'has status 422' do + expect(response.status).to eq 422 + end + + it 'returns message "Already confirmed"' do + expect(response_json).to eq({ message: 'Already confirmed', redirect_url: '/' }) + end + end + end +end From 70f08103aa6ddf5652f24b1846d1f3ebf316a23b Mon Sep 17 00:00:00 2001 From: Abhishek Date: Fri, 2 Oct 2020 19:03:59 +0800 Subject: [PATCH 26/57] feat: Activity message for label addition & deletion (#1291) Fixes: #1022 --- app/models/conversation.rb | 27 ++++++++++++++++++ config/locales/en.yml | 3 ++ spec/models/conversation_spec.rb | 47 +++++++++++++++++++++++++++++++- 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 32bcb4083..45093a3aa 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -156,6 +156,7 @@ class Conversation < ApplicationRecord create_status_change_message(user_name) if saved_change_to_status? create_assignee_change(user_name) if saved_change_to_assignee_id? + create_label_change(user_name) if saved_change_to_label_list? end def activity_message_params(content) @@ -216,6 +217,32 @@ class Conversation < ApplicationRecord messages.create(activity_message_params(content)) end + def create_label_change(user_name) + previous_labels, current_labels = previous_changes[:label_list] + return unless (previous_labels.is_a? Array) && (current_labels.is_a? Array) + + create_label_added(user_name, current_labels - previous_labels) + create_label_removed(user_name, previous_labels - current_labels) + end + + def create_label_added(user_name, labels = []) + return unless labels.size.positive? + + params = { user_name: user_name, labels: labels.join(', ') } + content = I18n.t('conversations.activity.labels.added', **params) + + messages.create(activity_message_params(content)) + end + + def create_label_removed(user_name, labels = []) + return unless labels.size.positive? + + params = { user_name: user_name, labels: labels.join(', ') } + content = I18n.t('conversations.activity.labels.removed', **params) + + messages.create(activity_message_params(content)) + end + def mute_key format('CONVERSATION::%d::MUTED', id: id) end diff --git a/config/locales/en.yml b/config/locales/en.yml index 56f1649b8..508d6e6e6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -51,6 +51,9 @@ en: self_assigned: "%{user_name} self-assigned this conversation" assigned: "Assigned to %{assignee_name} by %{user_name}" removed: "Conversation unassigned by %{user_name}" + labels: + added: "%{user_name} added %{labels}" + removed: "%{user_name} removed %{labels}" templates: greeting_message_body: "%{account_name} typically replies in a few hours." ways_to_reach_you_message_body: "Give the team a way to reach you." diff --git a/spec/models/conversation_spec.rb b/spec/models/conversation_spec.rb index f06105067..383bf972c 100644 --- a/spec/models/conversation_spec.rb +++ b/spec/models/conversation_spec.rb @@ -58,6 +58,7 @@ RSpec.describe Conversation, type: :model do create(:user, email: 'agent2@example.com', account: account, role: :agent) end let(:assignment_mailer) { double(deliver: true) } + let(:label) { create(:label, account: account) } before do conversation @@ -70,7 +71,8 @@ RSpec.describe Conversation, type: :model do status: :resolved, locked: true, contact_last_seen_at: Time.now, - assignee: new_assignee + assignee: new_assignee, + label_list: [label.title] ) end @@ -90,6 +92,7 @@ RSpec.describe Conversation, type: :model do # create_activity expect(conversation.messages.pluck(:content)).to include("Conversation was marked resolved by #{old_assignee.available_name}") expect(conversation.messages.pluck(:content)).to include("Assigned to #{new_assignee.available_name} by #{old_assignee.available_name}") + expect(conversation.messages.pluck(:content)).to include("#{old_assignee.available_name} added #{label.title}") end end @@ -195,6 +198,48 @@ RSpec.describe Conversation, type: :model do end end + describe '#update_labels' do + let(:account) { create(:account) } + let(:conversation) { create(:conversation, account: account) } + let(:agent) do + create(:user, email: 'agent@example.com', account: account, role: :agent) + end + let(:first_label) { create(:label, account: account) } + let(:second_label) { create(:label, account: account) } + let(:third_label) { create(:label, account: account) } + let(:fourth_label) { create(:label, account: account) } + + before do + conversation + Current.user = agent + + first_label + second_label + third_label + fourth_label + end + + it 'adds one label to conversation' do + labels = [first_label].map(&:title) + expect(conversation.update_labels(labels)).to eq(true) + expect(conversation.label_list).to match_array(labels) + expect(conversation.messages.pluck(:content)).to include("#{agent.available_name} added #{labels.join(', ')}") + end + + it 'adds and removes previously added labels' do + labels = [first_label, fourth_label].map(&:title) + expect(conversation.update_labels(labels)).to eq(true) + expect(conversation.label_list).to match_array(labels) + expect(conversation.messages.pluck(:content)).to include("#{agent.available_name} added #{labels.join(', ')}") + + updated_labels = [second_label, third_label].map(&:title) + expect(conversation.update_labels(updated_labels)).to eq(true) + expect(conversation.label_list).to match_array(updated_labels) + expect(conversation.messages.pluck(:content)).to include("#{agent.available_name} added #{updated_labels.join(', ')}") + expect(conversation.messages.pluck(:content)).to include("#{agent.available_name} removed #{labels.join(', ')}") + end + end + describe '#toggle_status' do subject(:toggle_status) { conversation.toggle_status } From db4997c07f085731882ffbdeece99eef1c34e6ac Mon Sep 17 00:00:00 2001 From: Zero King Date: Sat, 3 Oct 2020 16:37:50 +0800 Subject: [PATCH 27/57] fix: Array length check (#1300) --- app/javascript/dashboard/components/widgets/ChannelItem.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/dashboard/components/widgets/ChannelItem.vue b/app/javascript/dashboard/components/widgets/ChannelItem.vue index 39fa2e028..32989cc76 100644 --- a/app/javascript/dashboard/components/widgets/ChannelItem.vue +++ b/app/javascript/dashboard/components/widgets/ChannelItem.vue @@ -56,7 +56,7 @@ export default { computed: { isActive() { const { key } = this.channel; - if (Object.keys(this.enabledFeatures) === 0) { + if (Object.keys(this.enabledFeatures).length === 0) { return false; } if (key === 'facebook') { From 807f79ef5d03098f5f0f70e4937779c98b498700 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Sat, 3 Oct 2020 17:54:33 +0800 Subject: [PATCH 28/57] feat: support for Redis sentinel (#1301) closes: #925 --- .env.example | 5 +++++ config/initializers/redis.rb | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 281cd64d1..5a6ab50ba 100644 --- a/.env.example +++ b/.env.example @@ -22,6 +22,11 @@ REDIS_URL=redis://redis:6379 # which will be the password for the redis service running inside the docker-compose # to make it secure REDIS_PASSWORD= +# Redis Sentinel can be used by passing list of sentinel host and ports e,g. sentinel_host1:port1,sentinel_host2:port2 +REDIS_SENTINELS= +# Redis sentinel master name is required when using sentinel, default value is "mymaster". +# You can find list of master using "SENTINEL masters" command +REDIS_SENTINEL_MASTER_NAME= # Postgres Database config variables POSTGRES_HOST=postgres diff --git a/config/initializers/redis.rb b/config/initializers/redis.rb index 423e0500f..35739a1ab 100644 --- a/config/initializers/redis.rb +++ b/config/initializers/redis.rb @@ -2,8 +2,23 @@ app_redis_config = { url: URI.parse(ENV.fetch('REDIS_URL', 'redis://127.0.0.1:6379')), password: ENV.fetch('REDIS_PASSWORD', nil).presence } -redis = Rails.env.test? ? MockRedis.new : Redis.new(app_redis_config) +if ENV['REDIS_SENTINELS'].presence + default_sentinel_port = '26379' + # expected format for REDIS_SENTINELS url string is host1:port1, host2:port2 + sentinels = ENV['REDIS_SENTINELS'].split(',').map do |sentinel_url| + host, port = sentinel_url.split(':').map(&:strip) + { host: host, port: port || default_sentinel_port, password: app_redis_config[:password] } + end + + master_name = ENV.fetch('REDIS_SENTINEL_MASTER_NAME', 'mymaster') + # over-write redis url as redis://:@/ when using sentinel + # more at https://github.com/redis/redis-rb/issues/531#issuecomment-263501322 + app_redis_config[:url] = URI.parse("redis://#{master_name}") + app_redis_config[:sentinels] = sentinels +end + +redis = Rails.env.test? ? MockRedis.new : Redis.new(app_redis_config) # Alfred # Add here as you use it for more features # Used for Round Robin, Conversation Emails & Online Presence From 3d379b071d6b79a57d3e3ad5095c790a7abf356f Mon Sep 17 00:00:00 2001 From: Arman Date: Sun, 4 Oct 2020 16:25:22 +0530 Subject: [PATCH 29/57] feat: use gravatar if no avatar image (#1307) Fixes: #1240 --- app/models/user.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/models/user.rb b/app/models/user.rb index 33faa8ee6..45a87dac1 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -110,6 +110,15 @@ class User < ApplicationRecord inboxes.where(account_id: Current.account.id) end + alias avatar_img_url avatar_url + def avatar_url + if avatar_img_url == '' + hash = Digest::MD5.hexdigest(email) + return "https://www.gravatar.com/avatar/#{hash}" + end + avatar_img_url + end + def administrator? current_account_user&.administrator? end From df527088e90b5b0649099ccba22ae515d42fbbdc Mon Sep 17 00:00:00 2001 From: SarawutKl Date: Mon, 5 Oct 2020 00:27:11 +0700 Subject: [PATCH 30/57] fix: Replace Whatsapp inbox icon (#1311) --- app/javascript/dashboard/components/layout/Sidebar.vue | 1 + .../dashboard/components/layout/SidebarItem.vue | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/javascript/dashboard/components/layout/Sidebar.vue b/app/javascript/dashboard/components/layout/Sidebar.vue index c99816683..0b06cfdc0 100644 --- a/app/javascript/dashboard/components/layout/Sidebar.vue +++ b/app/javascript/dashboard/components/layout/Sidebar.vue @@ -258,6 +258,7 @@ export default { label: inbox.name, toState: frontendURL(`accounts/${this.accountId}/inbox/${inbox.id}`), type: inbox.channel_type, + phoneNumber: inbox.phone_number, })), }; }, diff --git a/app/javascript/dashboard/components/layout/SidebarItem.vue b/app/javascript/dashboard/components/layout/SidebarItem.vue index e75b40619..396033f76 100644 --- a/app/javascript/dashboard/components/layout/SidebarItem.vue +++ b/app/javascript/dashboard/components/layout/SidebarItem.vue @@ -59,7 +59,7 @@ import router from '../../routes'; import adminMixin from '../../mixins/isAdmin'; import { INBOX_TYPES } from 'shared/mixins/inboxMixin'; -const getInboxClassByType = type => { +const getInboxClassByType = (type, phoneNumber) => { switch (type) { case INBOX_TYPES.WEB: return 'ion-earth'; @@ -71,7 +71,9 @@ const getInboxClassByType = type => { return 'ion-social-twitter'; case INBOX_TYPES.TWILIO: - return 'ion-android-textsms'; + return phoneNumber.startsWith('whatsapp') + ? 'ion-social-whatsapp-outline' + : 'ion-android-textsms'; case INBOX_TYPES.API: return 'ion-cloud'; @@ -119,8 +121,8 @@ export default { }, methods: { computedInboxClass(child) { - const { type } = child; - const classByType = getInboxClassByType(type); + const { type, phoneNumber } = child; + const classByType = getInboxClassByType(type, phoneNumber); return classByType; }, newLinkClick() { From 1e1300c9d2ba94f640ab56c679953697b36f54ab Mon Sep 17 00:00:00 2001 From: Narendran Kannan Date: Mon, 5 Oct 2020 13:23:38 +0530 Subject: [PATCH 31/57] fix: Add word break for files with long name (#1306) --- .../dashboard/components/widgets/conversation/bubble/File.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/dashboard/components/widgets/conversation/bubble/File.vue b/app/javascript/dashboard/components/widgets/conversation/bubble/File.vue index d7ee7e108..14e01e276 100644 --- a/app/javascript/dashboard/components/widgets/conversation/bubble/File.vue +++ b/app/javascript/dashboard/components/widgets/conversation/bubble/File.vue @@ -63,6 +63,7 @@ export default { margin: 0; color: $color-white; font-weight: $font-weight-bold; + word-break: break-word; } .button { From 77d380dd6b31b24b6f837f31cfba0ea3ac0c1b01 Mon Sep 17 00:00:00 2001 From: Abhishek Date: Mon, 5 Oct 2020 20:01:10 +0800 Subject: [PATCH 32/57] chore: refactor redis config (#1310) - refactor Redis config in Redis::Config Module - unit tests for Redis::Config module --- config/initializers/redis.rb | 21 +---------- config/initializers/sidekiq.rb | 9 ++--- lib/redis/config.rb | 48 +++++++++++++++++++++++++ spec/lib/redis/config_spec.rb | 65 ++++++++++++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 27 deletions(-) create mode 100644 lib/redis/config.rb create mode 100644 spec/lib/redis/config_spec.rb diff --git a/config/initializers/redis.rb b/config/initializers/redis.rb index 35739a1ab..d1aa25b2e 100644 --- a/config/initializers/redis.rb +++ b/config/initializers/redis.rb @@ -1,24 +1,5 @@ -app_redis_config = { - url: URI.parse(ENV.fetch('REDIS_URL', 'redis://127.0.0.1:6379')), - password: ENV.fetch('REDIS_PASSWORD', nil).presence -} +redis = Rails.env.test? ? MockRedis.new : Redis.new(Redis::Config.app) -if ENV['REDIS_SENTINELS'].presence - default_sentinel_port = '26379' - # expected format for REDIS_SENTINELS url string is host1:port1, host2:port2 - sentinels = ENV['REDIS_SENTINELS'].split(',').map do |sentinel_url| - host, port = sentinel_url.split(':').map(&:strip) - { host: host, port: port || default_sentinel_port, password: app_redis_config[:password] } - end - - master_name = ENV.fetch('REDIS_SENTINEL_MASTER_NAME', 'mymaster') - # over-write redis url as redis://:@/ when using sentinel - # more at https://github.com/redis/redis-rb/issues/531#issuecomment-263501322 - app_redis_config[:url] = URI.parse("redis://#{master_name}") - app_redis_config[:sentinels] = sentinels -end - -redis = Rails.env.test? ? MockRedis.new : Redis.new(app_redis_config) # Alfred # Add here as you use it for more features # Used for Round Robin, Conversation Emails & Online Presence diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 8b535874e..7a28ca972 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -1,12 +1,7 @@ -sidekiq_redis_config = { - url: ENV.fetch('REDIS_URL', 'redis://127.0.0.1:6379'), - password: ENV.fetch('REDIS_PASSWORD', nil).presence -} - Sidekiq.configure_client do |config| - config.redis = sidekiq_redis_config.merge(size: 25) + config.redis = Redis::Config.sidekiq end Sidekiq.configure_server do |config| - config.redis = sidekiq_redis_config.merge(size: 25) + config.redis = Redis::Config.sidekiq end diff --git a/lib/redis/config.rb b/lib/redis/config.rb new file mode 100644 index 000000000..756b4bbee --- /dev/null +++ b/lib/redis/config.rb @@ -0,0 +1,48 @@ +module Redis::Config + DEFAULT_SENTINEL_PORT = '26379'.freeze + SIDEKIQ_SIZE = 25 + + class << self + def app + return BASE_CONFIG if const_defined? 'BASE_CONFIG' + + reload_config + end + + def sidekiq + config = begin + if const_defined? 'BASE_CONFIG' + BASE_CONFIG + else + reload_config + end + end + config.merge(size: SIDEKIQ_SIZE) + end + + def reload_config + config = { + url: ENV.fetch('REDIS_URL', 'redis://127.0.0.1:6379'), + password: ENV.fetch('REDIS_PASSWORD', nil).presence + } + + sentinel_string = ENV.fetch('REDIS_SENTINELS', nil) + if sentinel_string.presence + # expected format for REDIS_SENTINELS url string is host1:port1, host2:port2 + sentinels = sentinel_string.split(',').map do |sentinel_url| + host, port = sentinel_url.split(':').map(&:strip) + { host: host, port: port || DEFAULT_SENTINEL_PORT, password: config[:password] } + end + + master_name = ENV.fetch('REDIS_SENTINEL_MASTER_NAME', 'mymaster') + # over-write redis url as redis://:@/ when using sentinel + # more at https://github.com/redis/redis-rb/issues/531#issuecomment-263501322 + config[:url] = "redis://#{master_name}" + config[:sentinels] = sentinels + end + send(:remove_const, 'BASE_CONFIG') if const_defined? 'BASE_CONFIG' + const_set('BASE_CONFIG', config) + BASE_CONFIG + end + end +end diff --git a/spec/lib/redis/config_spec.rb b/spec/lib/redis/config_spec.rb new file mode 100644 index 000000000..d586c1bc3 --- /dev/null +++ b/spec/lib/redis/config_spec.rb @@ -0,0 +1,65 @@ +require 'rails_helper' + +describe ::Redis::Config do + context 'when single redis instance is used' do + let(:redis_url) { 'redis://my-redis-instance:6379' } + let(:redis_pasword) { 'some-strong-password' } + + before do + allow(ENV).to receive(:fetch).with('REDIS_URL', 'redis://127.0.0.1:6379').and_return(redis_url) + allow(ENV).to receive(:fetch).with('REDIS_PASSWORD', nil).and_return(redis_pasword) + allow(ENV).to receive(:fetch).with('REDIS_SENTINELS', nil).and_return('') + allow(ENV).to receive(:fetch).with('REDIS_SENTINEL_MASTER_NAME', 'mymaster').and_return('') + described_class.reload_config + end + + it 'checks for app redis config' do + app_config = described_class.app + expect(app_config.keys).to match_array([:url, :password]) + expect(app_config[:url]).to eq(redis_url) + expect(app_config[:password]).to eq(redis_pasword) + end + + it 'checks for sidekiq redis config' do + sidekiq_config = described_class.sidekiq + expect(sidekiq_config.keys).to match_array([:url, :password, :size]) + expect(sidekiq_config[:url]).to eq redis_url + expect(sidekiq_config[:password]).to eq redis_pasword + expect(sidekiq_config[:size]).to eq described_class::SIDEKIQ_SIZE + end + end + + context 'when redis sentinel is used' do + let(:redis_url) { 'redis://my-redis-instance:6379' } + let(:redis_sentinels) { 'sentinel_1:1234, sentinel_2:4321, sentinel_3' } + let(:redis_master_name) { 'master-name' } + let(:redis_pasword) { 'some-strong-password' } + + let(:expected_sentinels) do + [ + { host: 'sentinel_1', port: '1234', password: 'some-strong-password' }, + { host: 'sentinel_2', port: '4321', password: 'some-strong-password' }, + { host: 'sentinel_3', port: '26379', password: 'some-strong-password' } + ] + end + + before do + allow(ENV).to receive(:fetch).with('REDIS_URL', 'redis://127.0.0.1:6379').and_return(redis_url) + allow(ENV).to receive(:fetch).with('REDIS_PASSWORD', nil).and_return(redis_pasword) + allow(ENV).to receive(:fetch).with('REDIS_SENTINELS', nil).and_return(redis_sentinels) + allow(ENV).to receive(:fetch).with('REDIS_SENTINEL_MASTER_NAME', 'mymaster').and_return(redis_master_name) + described_class.reload_config + end + + it 'checks for app redis config' do + expect(described_class.app.keys).to match_array([:url, :password, :sentinels]) + expect(described_class.app[:url]).to eq("redis://#{redis_master_name}") + expect(described_class.app[:sentinels]).to match_array(expected_sentinels) + end + + it 'checks for sidekiq redis config' do + expect(described_class.sidekiq.keys).to match_array([:url, :password, :sentinels, :size]) + expect(described_class.sidekiq[:size]).to eq described_class::SIDEKIQ_SIZE + end + end +end From e44afa03f3b07a0f3e88fb041ac4e88fd321df65 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Mon, 5 Oct 2020 17:55:46 +0530 Subject: [PATCH 33/57] chore: Prevent i18n config bleeding across requests (#1214) --- app/controllers/api/v1/accounts/base_controller.rb | 1 - app/controllers/api/v1/widget/base_controller.rb | 3 +-- app/controllers/api/v1/widget/labels_controller.rb | 2 +- .../api/v1/widget/messages_controller.rb | 4 ++-- app/controllers/application_controller.rb | 13 ++++++++++--- app/mailers/application_mailer.rb | 13 +++++++++++-- 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/app/controllers/api/v1/accounts/base_controller.rb b/app/controllers/api/v1/accounts/base_controller.rb index 01a0746b7..d0ae4c31d 100644 --- a/app/controllers/api/v1/accounts/base_controller.rb +++ b/app/controllers/api/v1/accounts/base_controller.rb @@ -15,7 +15,6 @@ class Api::V1::Accounts::BaseController < Api::BaseController elsif @resource&.is_a?(AgentBot) account_accessible_for_bot?(account) end - switch_locale account account end diff --git a/app/controllers/api/v1/widget/base_controller.rb b/app/controllers/api/v1/widget/base_controller.rb index 4aa2ec138..a69e0282c 100644 --- a/app/controllers/api/v1/widget/base_controller.rb +++ b/app/controllers/api/v1/widget/base_controller.rb @@ -20,8 +20,7 @@ class Api::V1::Widget::BaseController < ApplicationController def set_web_widget @web_widget = ::Channel::WebWidget.find_by!(website_token: permitted_params[:website_token]) - @account = @web_widget.account - switch_locale @account + @current_account = @web_widget.account end def set_contact diff --git a/app/controllers/api/v1/widget/labels_controller.rb b/app/controllers/api/v1/widget/labels_controller.rb index 564fec8fc..52d1fa3d3 100644 --- a/app/controllers/api/v1/widget/labels_controller.rb +++ b/app/controllers/api/v1/widget/labels_controller.rb @@ -20,7 +20,7 @@ class Api::V1::Widget::LabelsController < Api::V1::Widget::BaseController private def label_defined_in_account? - label = @account.labels.find_by(title: permitted_params[:label]) + label = @current_account.labels&.find_by(title: permitted_params[:label]) label.present? end diff --git a/app/controllers/api/v1/widget/messages_controller.rb b/app/controllers/api/v1/widget/messages_controller.rb index a3fed9fb4..a4f33b563 100644 --- a/app/controllers/api/v1/widget/messages_controller.rb +++ b/app/controllers/api/v1/widget/messages_controller.rb @@ -89,10 +89,10 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController end def update_contact(email) - contact_with_email = @account.contacts.find_by(email: email) + contact_with_email = @current_account.contacts.find_by(email: email) if contact_with_email @contact = ::ContactMergeAction.new( - account: @account, + account: @current_account, base_contact: contact_with_email, mergee_contact: @contact ).perform diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 561a797c3..58246bb73 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -5,6 +5,7 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :null_session before_action :set_current_user, unless: :devise_controller? + around_action :switch_locale around_action :handle_with_exception, unless: :devise_controller? # after_action :verify_authorized @@ -64,15 +65,21 @@ class ApplicationController < ActionController::Base end def locale_from_account(account) + return unless account + I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil end - def switch_locale(account) + def switch_locale(&action) # priority is for locale set in query string (mostly for widget/from js sdk) locale ||= locale_from_params # if local is not set in param, lets try account - locale ||= locale_from_account(account) - I18n.locale = locale || I18n.default_locale + locale ||= locale_from_account(@current_account) + # if nothing works we rely on default locale + locale ||= I18n.default_locale + # ensure locale won't bleed into other requests + # https://guides.rubyonrails.org/i18n.html#managing-the-locale-across-requests + I18n.with_locale(locale, &action) end def pundit_user diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 278039bd4..bfc6b249e 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -3,6 +3,7 @@ class ApplicationMailer < ActionMailer::Base default from: ENV.fetch('MAILER_SENDER_EMAIL', 'accounts@chatwoot.com') before_action { ensure_current_account(params.try(:[], :account)) } + around_action :switch_locale layout 'mailer/base' # Fetch template from Database if available # Order: Account Specific > Installation Specific > Fallback to file @@ -51,12 +52,20 @@ class ApplicationMailer < ActionMailer::Base end def locale_from_account(account) + return unless account + I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil end def ensure_current_account(account) Current.account = account if account.present? - locale ||= locale_from_account(account) if account.present? - I18n.locale = locale || I18n.default_locale + end + + def switch_locale(&action) + locale ||= locale_from_account(Current.account) + locale ||= I18n.default_locale + # ensure locale won't bleed into other requests + # https://guides.rubyonrails.org/i18n.html#managing-the-locale-across-requests + I18n.with_locale(locale, &action) end end From ee119b2174b71804388287dd9e1949bd57c55d79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B1=20=C9=91=C9=B7=20=C9=96=CE=B5=C9=BE=CE=B5=C6=99?= Date: Mon, 5 Oct 2020 21:25:18 +0700 Subject: [PATCH 34/57] chore: Update translation for vietnamese (#1312) --- .../dashboard/i18n/locale/vi/agentMgmt.json | 104 ++++---- .../dashboard/i18n/locale/vi/cannedMgmt.json | 78 +++--- .../dashboard/i18n/locale/vi/chatlist.json | 44 ++-- .../dashboard/i18n/locale/vi/contact.json | 104 ++++---- .../i18n/locale/vi/conversation.json | 80 +++--- .../i18n/locale/vi/generalSettings.json | 36 +-- .../dashboard/i18n/locale/vi/inboxMgmt.json | 242 +++++++++--------- .../i18n/locale/vi/integrations.json | 62 ++--- .../dashboard/i18n/locale/vi/labelsMgmt.json | 72 +++--- .../dashboard/i18n/locale/vi/login.json | 20 +- .../dashboard/i18n/locale/vi/report.json | 34 +-- .../i18n/locale/vi/resetPassword.json | 12 +- .../i18n/locale/vi/setNewPassword.json | 20 +- .../dashboard/i18n/locale/vi/settings.json | 152 +++++------ .../dashboard/i18n/locale/vi/signup.json | 30 +-- .../dashboard/i18n/locale/vi/webhooks.json | 2 +- 16 files changed, 546 insertions(+), 546 deletions(-) diff --git a/app/javascript/dashboard/i18n/locale/vi/agentMgmt.json b/app/javascript/dashboard/i18n/locale/vi/agentMgmt.json index 2b2358c9a..375d317c4 100644 --- a/app/javascript/dashboard/i18n/locale/vi/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/vi/agentMgmt.json @@ -1,92 +1,92 @@ { "AGENT_MGMT": { - "HEADER": "Agents", - "HEADER_BTN_TXT": "Add Agent", - "LOADING": "Fetching Agent List", - "SIDEBAR_TXT": "

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.

", + "HEADER": "Nhà cung cấp", + "HEADER_BTN_TXT": "Thêm nhà cung cấp / đại lý", + "LOADING": "Tải danh sách nhà cung cấp", + "SIDEBAR_TXT": "

Nhà cung cấp

Một nhà cung cấp là thành viên của nhóm Hỗ trợ khách hàng của bạn.

Nhà cung cấp sẽ có thể xem và trả lời tin nhắn từ người dùng của bạn. Danh sách hiển thị tất cả các đại lý hiện có trong tài khoản của bạn.

Click vào Thêm nhà cung cấp để thêm mới nhà cung cấp. Nhà cung cấp mà bạn thêm sẽ nhận được email với liên kết xác nhận để kích hoạt tài khoản của họ, sau đó họ có thể truy cập Chatwoot và trả lời tin nhắn.

Quyền truy cập vào các tính năng của Chatwoot dựa trên các vai trò sau.

Nhà cung cấp - Nhà cung cấp có vai trò này chỉ có thể truy cập hộp thư đến, báo cáo và cuộc hội thoại. Họ có thể chỉ định các cuộc trò chuyện cho các đại lý khác hoặc chính họ và giải quyết các cuộc trò chuyện.

Administrator - Quản trị viên sẽ có quyền truy cập vào tất cả các tính năng Chatwoot được kích hoạt cho tài khoản của bạn, bao gồm cài đặt, cùng với tất cả các đặc quyền của một đại lý bình thường.

", "AGENT_TYPES": { - "ADMINISTRATOR": "Administrator", - "AGENT": "Agent" + "ADMINISTRATOR": "Quản trị viên", + "AGENT": "Nhà cung cấp" }, "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", + "404": "Không có tác nhân nào được liên kết với tài khoản này", + "TITLE": "Quản lý các đại lý trong nhóm của bạn", + "DESC": "Bạn có thể thêm / bớt đại lý vào / trong nhóm của mình.", + "NAME": "Tên", "EMAIL": "EMAIL", - "STATUS": "Status", - "ACTIONS": "Actions", - "VERIFIED": "Verified", - "VERIFICATION_PENDING": "Verification Pending" + "STATUS": "Trạng thái", + "ACTIONS": "Hành động", + "VERIFIED": "Đã xác minh", + "VERIFICATION_PENDING": "Đang chờ xác minh" }, "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", + "TITLE": "Thêm đại lý vào nhóm của bạn", + "DESC": "Bạn có thể thêm những người có thể xử lý hỗ trợ cho hộp thư đến của bạn.", + "CANCEL_BUTTON_TEXT": "Huỷ", "FORM": { "NAME": { - "LABEL": "Agent Name", - "PLACEHOLDER": "Please enter a name of the agent" + "LABEL": "Tên nhà cung cấp", + "PLACEHOLDER": "Vui lòng nhập tên nhà cung cấp" }, "AGENT_TYPE": { - "LABEL": "Agent Type", - "PLACEHOLDER": "Please select a type", - "ERROR": "Agent type is required" + "LABEL": "Loại nhà cung cấp", + "PLACEHOLDER": "Vui lòng chọn loại", + "ERROR": "Loại nhà cung cấp là bắt buộc" }, "EMAIL": { - "LABEL": "Email Address", - "PLACEHOLDER": "Please enter an email address of the agent" + "LABEL": "Email", + "PLACEHOLDER": "Vui lòng nhập email" }, - "SUBMIT": "Add Agent" + "SUBMIT": "Thêm nhà cung cấp" }, "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" + "SUCCESS_MESSAGE": "Nhà cung cấp được thêm thành công", + "EXIST_MESSAGE": "Email đã được sử dung, vui lòng chọn email khác.", + "ERROR_MESSAGE": "Không thể kết nối với Máy chủ Woot, Vui lòng thử lại sau" } }, "DELETE": { - "BUTTON_TEXT": "Delete", + "BUTTON_TEXT": "Xoá", "API": { - "SUCCESS_MESSAGE": "Agent deleted successfully", - "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + "SUCCESS_MESSAGE": "Nhà cung cấp được xoá thành công", + "ERROR_MESSAGE": "Không thể kết nối với Máy chủ Woot, Vui lòng thử lại sau" }, "CONFIRM": { - "TITLE": "Confirm Deletion", - "MESSAGE": "Are you sure to delete ", - "YES": "Yes, Delete ", - "NO": "No, Keep " + "TITLE": "Xác nhận xoá", + "MESSAGE": "Bạn có muốn xoá? ", + "YES": "Có, xoá ", + "NO": "Không, giữ " } }, "EDIT": { - "TITLE": "Edit agent", + "TITLE": "Chỉnh sửa nhà cung cấp", "FORM": { "NAME": { - "LABEL": "Agent Name", - "PLACEHOLDER": "Please enter a name of the agent" + "LABEL": "Tên nhà cung cấp", + "PLACEHOLDER": "Vui lòng nhập tên nhà cung cấp" }, "AGENT_TYPE": { - "LABEL": "Agent Type", - "PLACEHOLDER": "Please select a type", - "ERROR": "Agent type is required" + "LABEL": "Loại nhà cung cấp", + "PLACEHOLDER": "Vui lòng chọn loại", + "ERROR": "Loại nhà cung cấp là bắt buộc" }, "EMAIL": { - "LABEL": "Email Address", - "PLACEHOLDER": "Please enter an email address of the agent" + "LABEL": "Emails", + "PLACEHOLDER": "Vui lòng nhập email" }, - "SUBMIT": "Edit Agent" + "SUBMIT": "Chỉnh sửa nhà cung cấp" }, - "BUTTON_TEXT": "Edit", - "CANCEL_BUTTON_TEXT": "Cancel", + "BUTTON_TEXT": "Chỉnh sửa", + "CANCEL_BUTTON_TEXT": "Huỷ", "API": { - "SUCCESS_MESSAGE": "Agent updated successfully", - "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + "SUCCESS_MESSAGE": "Nhà cung cấp được cập nhật thành công", + "ERROR_MESSAGE": "Không thể kết nối với Máy chủ Woot, Vui lòng thử lại sau" }, "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" + "ADMIN_RESET_BUTTON": "Đặt lại mật khẩu", + "ADMIN_SUCCESS_MESSAGE": "Một email với hướng dẫn đặt lại mật khẩu đã được gửi đến đại lý", + "SUCCESS_MESSAGE": "Đặt lại mật khẩu đại lý thành công", + "ERROR_MESSAGE": "Không thể kết nối với Máy chủ Woot, Vui lòng thử lại sau" } }, "SEARCH": { diff --git a/app/javascript/dashboard/i18n/locale/vi/cannedMgmt.json b/app/javascript/dashboard/i18n/locale/vi/cannedMgmt.json index bcab1dc6a..4e13e6765 100644 --- a/app/javascript/dashboard/i18n/locale/vi/cannedMgmt.json +++ b/app/javascript/dashboard/i18n/locale/vi/cannedMgmt.json @@ -1,75 +1,75 @@ { "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.

", + "HEADER": "Thư mẫu phẩn hồi", + "HEADER_BTN_TXT": "Thêm thư mẫu phẩn hồi", + "LOADING": "Đang tải thư mẫu phẩn hồi", + "SEARCH_404": "Không có kết quả nào được tìm thấy", + "SIDEBAR_TXT": "

Thư mẫu phẩn hồi

Thư mẫu phẩn hồi là các mẫu trả lời đã lưu có thể được sử dụng để nhanh chóng gửi trả lời cho một cuộc trò chuyện .

Để tạo một Thư mẫu phẩn hồi, chỉ cần click vào Thêm Thư mẫu phẩn hồi. Bạn cũng có thể xoá Thư mẫu phẩn hồi bởi button Xoá

Thư mẫu phẩn hồi được sử dụng với sự giúp đỡ của Short Codes. Nhân viên có thể truy cập câu trả lời soạn trước khi trò chuyện bằng cách nhập '/'.

", "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.", + "404": "Không có câu trả lời soạn trước nào có sẵn trong tài khoản này.", + "TITLE": "Quản lý thư mẫu phẩn hồi", + "DESC": "Thư mẫu phẩn hồi là các mẫu trả lời được xác định trước có thể được sử dụng để nhanh chóng gửi trả lời cho vé.", "TABLE_HEADER": [ "Short Code", - "Content", - "Actions" + "Nội dung", + "Hành động" ] }, "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", + "TITLE": "Thêm thư mẫu phẩn hồi", + "DESC": "Thư mẫu phẩn hồi là các mẫu trả lời đã lưu có thể được sử dụng để nhanh chóng gửi trả lời cho cuộc trò chuyện.", + "CANCEL_BUTTON_TEXT": "Huỷ", "FORM": { "SHORT_CODE": { "LABEL": "Short Code", - "PLACEHOLDER": "Please enter a shortcode", - "ERROR": "Short Code is required" + "PLACEHOLDER": "Vui lòng nhập một shortcode", + "ERROR": "Short Code là bắt buộc" }, "CONTENT": { - "LABEL": "Content", - "PLACEHOLDER": "Please enter a content", - "ERROR": "Content is required" + "LABEL": "Nội dung", + "PLACEHOLDER": "Vui lòng nhập nội dung", + "ERROR": "Nội dung là bắt buộc" }, - "SUBMIT": "Submit" + "SUBMIT": "Gửi" }, "API": { - "SUCCESS_MESSAGE": "Canned Response added successfully", - "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + "SUCCESS_MESSAGE": "Thư mẫu phẩn hồi được thêm thành công", + "ERROR_MESSAGE": "Không thể kết nối với Máy chủ Woot, Vui lòng thử lại sau" } }, "EDIT": { - "TITLE": "Edit Canned Response", - "CANCEL_BUTTON_TEXT": "Cancel", + "TITLE": "Chỉnh sửa Thư mẫu phẩn hồi", + "CANCEL_BUTTON_TEXT": "Huỷ", "FORM": { "SHORT_CODE": { "LABEL": "Short Code", - "PLACEHOLDER": "Please enter a shortcode", - "ERROR": "Short Code is required" + "PLACEHOLDER": "Vui lòng nhập shortcode", + "ERROR": "Short Code là bắt buộc" }, "CONTENT": { - "LABEL": "Content", - "PLACEHOLDER": "Please enter a content", - "ERROR": "Content is required" + "LABEL": "Nội dung", + "PLACEHOLDER": "Vui lòng nhập nội dung", + "ERROR": "Nội dung là bắt buộc" }, - "SUBMIT": "Submit" + "SUBMIT": "Gửi" }, - "BUTTON_TEXT": "Edit", + "BUTTON_TEXT": "Chỉnh sửa", "API": { - "SUCCESS_MESSAGE": "Canned Response updated successfully", - "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + "SUCCESS_MESSAGE": "Thư mẫu phẩn hồi cập nhật thành công", + "ERROR_MESSAGE": "Không thể kết nối với Máy chủ Woot, Vui lòng thử lại sau" } }, "DELETE": { - "BUTTON_TEXT": "Delete", + "BUTTON_TEXT": "Xoá", "API": { - "SUCCESS_MESSAGE": "Canned response deleted successfully", - "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + "SUCCESS_MESSAGE": "Thư mẫu phẩn hồi đã được xoá thành công", + "ERROR_MESSAGE": "Không thể kết nối với Máy chủ Woot, Vui lòng thử lại sau" }, "CONFIRM": { - "TITLE": "Confirm Deletion", - "MESSAGE": "Are you sure to delete ", - "YES": "Yes, Delete ", - "NO": "No, Keep " + "TITLE": "Xác nhận xoá", + "MESSAGE": "Bạn có chắc muốn xoá? ", + "YES": "Có, xoá ", + "NO": "Không, giữ " } } } diff --git a/app/javascript/dashboard/i18n/locale/vi/chatlist.json b/app/javascript/dashboard/i18n/locale/vi/chatlist.json index a0b31427f..08dac6f2c 100644 --- a/app/javascript/dashboard/i18n/locale/vi/chatlist.json +++ b/app/javascript/dashboard/i18n/locale/vi/chatlist.json @@ -1,49 +1,49 @@ { "CHAT_LIST": { - "LOADING": "Fetching conversations", - "LOAD_MORE_CONVERSATIONS": "Load more conversations", - "EOF": "All conversations loaded 🎉", + "LOADING": "Đang tải các cuộc trò chuyện", + "LOAD_MORE_CONVERSATIONS": "Tải thêm các cuộc trò chuyện", + "EOF": "Tất cả các cuộc trò chuyện đã được tải 🎉", "LIST": { - "404": "There are no active conversations in this group." + "404": "Không có bất kì hoạt động trò chuyện nào trong nhóm." }, - "TAB_HEADING": "Conversations", + "TAB_HEADING": "Các cuộc hội thoại", "SEARCH": { - "INPUT": "Search for People, Chats, Saved Replies .." + "INPUT": "Tìm kiếm cho người, đoạn chat, lưu trữ trả lời .." }, "STATUS_TABS": [ { - "NAME": "Open", + "NAME": "Mở", "KEY": "openCount" }, { - "NAME": "Resolved", + "NAME": "Đã được giải quyết", "KEY": "allConvCount" } ], "ASSIGNEE_TYPE_TABS": [ { - "NAME": "Mine", + "NAME": "Của tôi", "KEY": "me", "COUNT_KEY": "mineCount" }, { - "NAME": "Unassigned", + "NAME": "Chưa được phân công", "KEY": "unassigned", "COUNT_KEY": "unAssignedCount" }, { - "NAME": "All", + "NAME": "Tất cả", "KEY": "all", "COUNT_KEY": "allCount" } ], "CHAT_STATUS_ITEMS": [ { - "TEXT": "Open", + "TEXT": "Mở", "VALUE": "open" }, { - "TEXT": "Resolved", + "TEXT": "Đã được giải quyết", "VALUE": "resolved" }, { @@ -54,31 +54,31 @@ "ATTACHMENTS": { "image": { "ICON": "ion-image", - "CONTENT": "Picture message" + "CONTENT": "Tin nhắn hình ảnh" }, "audio": { "ICON": "ion-volume-high", - "CONTENT": "Audio message" + "CONTENT": "Tin nhắn thoại" }, "video": { "ICON": "ion-ios-videocam", - "CONTENT": "Video message" + "CONTENT": "Tin nhắn video" }, "file": { "ICON": "ion-document", - "CONTENT": "File Attachment" + "CONTENT": "Tập tin đính kèm" }, "location": { "ICON": "ion-ios-location", - "CONTENT": "Location" + "CONTENT": "Địa điểm" }, "fallback": { "ICON": "ion-link", - "CONTENT": "has shared a url" + "CONTENT": "có một đường dẫn chia sẻ" } }, - "RECEIVED_VIA_EMAIL": "Received via email", - "VIEW_TWEET_IN_TWITTER": "View tweet in Twitter", - "REPLY_TO_TWEET": "Reply to this tweet" + "RECEIVED_VIA_EMAIL": "Nhận được từ email", + "VIEW_TWEET_IN_TWITTER": "Xem tweet trên Twitter", + "REPLY_TO_TWEET": "Trả lời cho tweet này" } } diff --git a/app/javascript/dashboard/i18n/locale/vi/contact.json b/app/javascript/dashboard/i18n/locale/vi/contact.json index 0e21c0d5d..18ee5b808 100644 --- a/app/javascript/dashboard/i18n/locale/vi/contact.json +++ b/app/javascript/dashboard/i18n/locale/vi/contact.json @@ -1,96 +1,96 @@ { "CONTACT_PANEL": { - "NOT_AVAILABLE": "Not Available", - "EMAIL_ADDRESS": "Email Address", - "PHONE_NUMBER": "Phone number", - "COMPANY": "Company", - "LOCATION": "Location", - "CONVERSATION_TITLE": "Conversation Details", - "BROWSER": "Browser", - "OS": "Operating System", - "INITIATED_FROM": "Initiated from", - "INITIATED_AT": "Initiated at", + "NOT_AVAILABLE": "Không có sẵn", + "EMAIL_ADDRESS": "Địa chỉ email", + "PHONE_NUMBER": "Số điện thoại", + "COMPANY": "Công ty", + "LOCATION": "Vị trí", + "CONVERSATION_TITLE": "Chi tiết của cuộc trò chuyện", + "BROWSER": "Trình duyệt", + "OS": "Hệ điều hành", + "INITIATED_FROM": "Bắt đầu từ", + "INITIATED_AT": "Bắt đầu lúc", "CONVERSATIONS": { - "NO_RECORDS_FOUND": "There are no previous conversations associated to this contact.", - "TITLE": "Previous Conversations" + "NO_RECORDS_FOUND": "Không có cuộc trò chuyện trước đó được liên kết với liên hệ này.", + "TITLE": "Cuộc trò chuyện trước đó" }, "CUSTOM_ATTRIBUTES": { - "TITLE": "Custom Attributes" + "TITLE": "Thuộc tính tùy chỉnh" }, "LABELS": { - "TITLE": "Conversation Labels", + "TITLE": "Nhãn hội thoại", "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." + "TITLE": "Nhãn cho", + "ACTIVE_LABELS": "Đã thêm nhãn vào cuộc trò chuyện", + "INACTIVE_LABELS": "Các nhãn có sẵn trong tài khoản", + "REMOVE": "Click vào X icon xóa nhãn", + "ADD": "Click vào + icon thêm nhãn", + "UPDATE_BUTTON": "Cập nhật nhãn", + "UPDATE_ERROR": "Không thể cập nhật nhãn, hãy thử lại." }, - "NO_LABELS_TO_ADD": "There are no more labels defined in the account.", - "NO_AVAILABLE_LABELS": "There are no labels added to this conversation." + "NO_LABELS_TO_ADD": "Không có thêm nhãn nào được xác định trong tài khoản.", + "NO_AVAILABLE_LABELS": "Không có nhãn nào được thêm vào cuộc trò chuyện này." }, - "MUTE_CONTACT": "Mute Conversation", - "MUTED_SUCCESS": "This conversation is muted for 6 hours", - "SEND_TRANSCRIPT": "Send Transcript", - "EDIT_LABEL": "Edit" + "MUTE_CONTACT": "Tắt tiếng cuộc trò chuyện", + "MUTED_SUCCESS": "Cuộc trò chuyện này bị tắt tiếng trong 6 giờ", + "SEND_TRANSCRIPT": "Gửi bản ghi", + "EDIT_LABEL": "Chỉnh sửa" }, "EDIT_CONTACT": { - "BUTTON_LABEL": "Edit Contact", - "TITLE": "Edit contact", - "DESC": "Edit contact details", + "BUTTON_LABEL": "Chỉnh sửa liên hệ", + "TITLE": "Chỉnh sửa liên hệ", + "DESC": "Chỉnh sửa liên hệ chi tiết", "FORM": { - "SUBMIT": "Submit", - "CANCEL": "Cancel", + "SUBMIT": "Gửi", + "CANCEL": "Huỷ", "AVATAR": { - "LABEL": "Contact Avatar" + "LABEL": "Avatar của liên hệ" }, "NAME": { - "PLACEHOLDER": "Enter the full name of the contact", - "LABEL": "Full Name" + "PLACEHOLDER": "Nhập tên đầy đủ của liên hệ", + "LABEL": "Tên đầy đủ" }, "BIO": { - "PLACEHOLDER": "Enter the bio of the contact", - "LABEL": "Bio" + "PLACEHOLDER": "Nhập tiểu sử của liên hệ", + "LABEL": "Tiểu sử" }, "EMAIL_ADDRESS": { - "PLACEHOLDER": "Enter the email address of the contact", - "LABEL": "Email Address" + "PLACEHOLDER": "Nhập địa chỉ email của liên hệ", + "LABEL": "Email" }, "PHONE_NUMBER": { - "PLACEHOLDER": "Enter the phone number of the contact", - "LABEL": "Phone Number" + "PLACEHOLDER": "Nhập số điện thoại của liên hệ", + "LABEL": "Số điện thoại" }, "LOCATION": { - "PLACEHOLDER": "Enter the location of the contact", - "LABEL": "Location" + "PLACEHOLDER": "Nhập vị trí của liên hệ", + "LABEL": "Vị trí" }, "COMPANY_NAME": { - "PLACEHOLDER": "Enter the company name", - "LABEL": "Company Name" + "PLACEHOLDER": "Nhập tên công ty", + "LABEL": "Tên công ty" }, "SOCIAL_PROFILES": { "FACEBOOK": { - "PLACEHOLDER": "Enter the Facebook username", + "PLACEHOLDER": "Nhập tên người dùng Facebook", "LABEL": "Facebook" }, "TWITTER": { - "PLACEHOLDER": "Enter the Twitter username", + "PLACEHOLDER": "Nhập tên người dùng Twitter", "LABEL": "Twitter" }, "LINKEDIN": { - "PLACEHOLDER": "Enter the LinkedIn username", + "PLACEHOLDER": "Nhập tên người dùng LinkedIn", "LABEL": "LinkedIn" }, "GITHUB": { - "PLACEHOLDER": "Enter the Github username", + "PLACEHOLDER": "Nhập tên người dùng Github", "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" + "SUCCESS_MESSAGE": "Đã cập nhật liên hệ thành công", + "CONTACT_ALREADY_EXIST": "Địa chỉ email này đang được sử dụng cho một liên hệ khác.", + "ERROR_MESSAGE": "Đã xảy ra lỗi khi cập nhật địa chỉ liên hệ, vui lòng thử lại" } } diff --git a/app/javascript/dashboard/i18n/locale/vi/conversation.json b/app/javascript/dashboard/i18n/locale/vi/conversation.json index f8ea61643..e6eb0622c 100644 --- a/app/javascript/dashboard/i18n/locale/vi/conversation.json +++ b/app/javascript/dashboard/i18n/locale/vi/conversation.json @@ -1,56 +1,56 @@ { "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", - "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", + "404": "Vui lòng chọn một cuộc trò chuyện từ ngăn bên trái", + "NO_MESSAGE_1": "Uh oh! Có vẻ như không có tin nhắn nào từ khách hàng trong hộp thư đến của bạn.", + "NO_MESSAGE_2": " gửi tin nhắn đến trang của bạn!", + "NO_INBOX_1": "Hola! Có vẻ như bạn chưa thêm bất kỳ hộp thư đến nào.", + "NO_INBOX_2": " để bắt đầu", + "NO_INBOX_AGENT": "Uh Oh! Có vẻ như bạn không thuộc bất kỳ hộp thư đến nào. Vui lòng liên hệ với quản trị viên của bạn", + "CLICK_HERE": "Click vào đây", + "LOADING_INBOXES": "Đang tải hộp thư đến", + "LOADING_CONVERSATIONS": "Đang tải cuộc trò chuyện", + "CANNOT_REPLY": "Bạn không thể trả lời do", + "24_HOURS_WINDOW": "Giới hạn thời lượng tin nhắn 24 giờ", + "LAST_INCOMING_TWEET": "Bạn đang trả lời tweet đến cuối cùng", + "REPLYING_TO": "Bạn đang trả lời:", + "REMOVE_SELECTION": "Xóa lựa chọn", + "DOWNLOAD": "Tải xuống", "HEADER": { - "RESOLVE_ACTION": "Resolve", - "REOPEN_ACTION": "Reopen", - "OPEN": "More", - "CLOSE": "Close", - "DETAILS": "details" + "RESOLVE_ACTION": "Giải quyết", + "REOPEN_ACTION": "Mở lại", + "OPEN": "Nhiều", + "CLOSE": "Đóng", + "DETAILS": "chi tiết" }, "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" + "MSG_INPUT": "Shift + enter cho dòng mới. Bắt đầu với '/' để chọn Câu trả lời soạn trước.", + "PRIVATE_MSG_INPUT": "Shift + enter cho dòng mới. Điều này sẽ chỉ hiển thị cho Đại lý" }, "REPLYBOX": { - "REPLY": "Reply", - "PRIVATE_NOTE": "Private Note", - "SEND": "Send", - "CREATE": "Add Note", + "REPLY": "Trả lời", + "PRIVATE_NOTE": "Lưu ý riêng", + "SEND": "Gửi", + "CREATE": "Thêm ghi chú", "TWEET": "Tweet" }, - "VISIBLE_TO_AGENTS": "Private Note: Only visible to you and your team", - "CHANGE_STATUS": "Conversation status changed", - "CHANGE_AGENT": "Conversation Assignee changed" + "VISIBLE_TO_AGENTS": "Lưu ý riêng: Chỉ hiển thị với bạn và nhóm của bạn", + "CHANGE_STATUS": "Trạng thái cuộc trò chuyện đã thay đổi", + "CHANGE_AGENT": "Người Được Chỉ Định Cuộc Hội Thoại đã thay đổi" }, "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", + "TITLE": "Gửi bản ghi cuộc trò chuyện", + "DESC": "Gửi bản sao của bản ghi cuộc trò chuyện đến địa chỉ email được chỉ định", + "SUBMIT": "Gửi", + "CANCEL": "Huỷ", + "SEND_EMAIL_SUCCESS": "Bản ghi cuộc trò chuyện đã được gửi thành công", + "SEND_EMAIL_ERROR": "Có lỗi, xin vui lòng thử lại", "FORM": { - "SEND_TO_CONTACT": "Send the transcript to the customer", - "SEND_TO_AGENT": "Send the transcript of the assigned agent", - "SEND_TO_OTHER_EMAIL_ADDRESS": "Send the transcript to another email address", + "SEND_TO_CONTACT": "Gửi bảng điểm cho khách hàng", + "SEND_TO_AGENT": "Gửi bản ghi của đại lý được chỉ định", + "SEND_TO_OTHER_EMAIL_ADDRESS": "Gửi bản ghi tới một địa chỉ email khác", "EMAIL": { - "PLACEHOLDER": "Enter an email address", - "ERROR": "Please enter a valid email address" + "PLACEHOLDER": "Nhập địa chỉ emai", + "ERROR": "Vui lòng nhập một địa chỉ email hợp lệ" } } } diff --git a/app/javascript/dashboard/i18n/locale/vi/generalSettings.json b/app/javascript/dashboard/i18n/locale/vi/generalSettings.json index 0a3fc0b8e..ddd4f9c2f 100644 --- a/app/javascript/dashboard/i18n/locale/vi/generalSettings.json +++ b/app/javascript/dashboard/i18n/locale/vi/generalSettings.json @@ -1,41 +1,41 @@ { "GENERAL_SETTINGS": { - "TITLE": "Account settings", - "SUBMIT": "Update settings", - "BACK": "Back", + "TITLE": "Cài đặt tài khoản", + "SUBMIT": "Cập nhật cài đặt", + "BACK": "Trờ về", "UPDATE": { - "ERROR": "Could not update settings, try again!", - "SUCCESS": "Successfully updated account settings" + "ERROR": "Không thể cập nhật cài đặt, hãy thử lại!", + "SUCCESS": "Đã cập nhật thành công cài đặt tài khoản" }, "FORM": { - "ERROR": "Please fix form errors", + "ERROR": "Vui lòng sửa lỗi biểu mẫu", "GENERAL_SECTION": { - "TITLE": "General settings", + "TITLE": "Cài đặt chung", "NOTE": "" }, "NAME": { - "LABEL": "Account name", - "PLACEHOLDER": "Your account name", - "ERROR": "Please enter a valid account name" + "LABEL": "Tên tài khoản", + "PLACEHOLDER": "Tên tài khoản của bạn", + "ERROR": "Vui lòng nhập tên tài khoản hợp lệ" }, "LANGUAGE": { - "LABEL": "Site language (Beta)", - "PLACEHOLDER": "Your account name", + "LABEL": "Ngôn ngữ trang web (Beta)", + "PLACEHOLDER": "Tên tài khoản của bạn", "ERROR": "" }, "DOMAIN": { - "LABEL": "Incoming Email Domain", - "PLACEHOLDER": "The domain where you will receive the emails", + "LABEL": "Tên miền email đến", + "PLACEHOLDER": "Miền mà bạn sẽ nhận được email", "ERROR": "" }, "SUPPORT_EMAIL": { - "LABEL": "Support Email", - "PLACEHOLDER": "Your company's support email", + "LABEL": "Email hỗ trợ", + "PLACEHOLDER": "Email hỗ trợ của công ty bạn", "ERROR": "" }, "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." + "INBOUND_EMAIL_ENABLED": "Tính liên tục của cuộc trò chuyện với email được kích hoạt cho tài khoản của bạn.", + "CUSTOM_EMAIL_DOMAIN_ENABLED": "Bạn có thể nhận email trong miền tùy chỉnh của mình ngay bây giờ." } } } diff --git a/app/javascript/dashboard/i18n/locale/vi/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/vi/inboxMgmt.json index fb4c71134..562b3b5ee 100644 --- a/app/javascript/dashboard/i18n/locale/vi/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/vi/inboxMgmt.json @@ -1,238 +1,238 @@ { "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.

", + "HEADER": "Hộp thư đến", + "SIDEBAR_TXT": "

Hộp thư đến

Khi bạn kết nối một trang web hoặc một Trang facebook với Chatwoot, nó được gọi là Hộp thư đến. Bạn có thể có hộp thư đến không giới hạn trong tài khoản Chatwoot của mình.

Click vào Thêm hộp thư đến để kết nối một trang web hoặc một Trang Facebook.

Trong Trang tổng quan, bạn có thể xem tất cả các cuộc hội thoại từ tất cả các hộp thư đến của mình ở một nơi duy nhất và trả lời chúng trong tab `Cuộc trò chuyện`.

Bạn cũng có thể xem các cuộc hội thoại dành riêng cho một hộp thư đến bằng cách nhấp vào tên hộp thư đến trên ngăn bên trái của trang tổng quan.

", "LIST": { - "404": "There are no inboxes attached to this account." + "404": "Không có hộp thư đến nào được đính kèm với tài khoản này." }, "CREATE_FLOW": [ { - "title": "Choose Channel", + "title": "Chọn kênh", "route": "settings_inbox_new", - "body": "Choose the provider you want to integrate with Chatwoot." + "body": "Chọn nhà cung cấp bạn muốn tích hợp với Chatwoot." }, { - "title": "Create Inbox", + "title": "Tạo Hộp thư đến", "route": "settings_inboxes_page_channel", - "body": "Authenticate your account and create an inbox." + "body": "Xác thực tài khoản của bạn và tạo hộp thư đến." }, { - "title": "Add Agents", + "title": "Thêm đại lý", "route": "settings_inboxes_add_agents", - "body": "Add agents to the created inbox." + "body": "Thêm đại lý vào hộp thư đến đã tạo." }, { "title": "Voila!", "route": "settings_inbox_finish", - "body": "You are all set to go!" + "body": "Bạn đã sẵn sàng để đi!" } ], "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" + "HELP": "Tái bút: Bằng cách đăng nhập, chúng tôi chỉ có quyền truy cập vào các tin nhắn trên Trang của bạn. Chatwoot không bao giờ có thể truy cập tin nhắn riêng tư của bạn.", + "CHOOSE_PAGE": "Chọn trang", + "CHOOSE_PLACEHOLDER": "Chọn một trang từ danh sách", + "INBOX_NAME": "Tên hộp thư đến", + "ADD_NAME": "Thêm tên cho hộp thư đến của bạn", + "PICK_NAME": "Chọn tên Hộp thư đến của bạn", + "PICK_A_VALUE": "Chọn một giá trị" }, "TWITTER": { - "HELP": "To add your Twitter profile as a channel, you need to authenticate your Twitter Profile by clicking on 'Sign in with Twitter' " + "HELP": "Để thêm hồ sơ Twitter của bạn làm kênh, bạn cần xác thực Hồ sơ Twitter của mình bằng cách nhấp vào 'Đăng nhập bằng 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", + "TITLE": "Kênh trang web", + "DESC": "Tạo kênh cho trang web của bạn và bắt đầu hỗ trợ khách hàng của bạn thông qua widget trang web của chúng tôi.", + "LOADING_MESSAGE": "Tạo kênh hỗ trợ trang web", "CHANNEL_AVATAR": { - "LABEL": "Channel Avatar" + "LABEL": "Hình đại diện kênh" }, "CHANNEL_NAME": { - "LABEL": "Website Name", - "PLACEHOLDER": "Enter your website name (eg: Acme Inc)" + "LABEL": "Tên trang web", + "PLACEHOLDER": "Nhập tên trang web của bạn (ví dụ: Acme Inc)" }, "CHANNEL_DOMAIN": { - "LABEL": "Website Domain", - "PLACEHOLDER": "Enter your website domain (eg: acme.com)" + "LABEL": "Domain trang web", + "PLACEHOLDER": "Nhập tên miền trang web của bạn (ví dụ: acme.com)" }, "CHANNEL_WELCOME_TITLE": { - "LABEL": "Welcome Heading", - "PLACEHOLDER": "Hi there !" + "LABEL": "Tiêu đề chào mừng", + "PLACEHOLDER": "Chào bạn !" }, "CHANNEL_WELCOME_TAGLINE": { - "LABEL": "Welcome Tagline", - "PLACEHOLDER": "We make it simple to connect with us. Ask us anything, or share your feedback." + "LABEL": "Dòng giới thiệu chào mừng", + "PLACEHOLDER": "Chúng tôi làm cho việc kết nối với chúng tôi trở nên đơn giản. Hỏi chúng tôi bất cứ điều gì hoặc chia sẻ phản hồi của bạn." }, "CHANNEL_GREETING_MESSAGE": { - "LABEL": "Channel greeting message", - "PLACEHOLDER": "Acme Inc typically replies in a few hours." + "LABEL": "Tin nhắn chúc mừng kênh", + "PLACEHOLDER": "Acme Inc thường trả lời sau vài giờ." }, "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" + "LABEL": "Bật lời chào kênh", + "HELP_TEXT": "Gửi tin nhắn chào mừng đến người dùng khi anh ta bắt đầu cuộc trò chuyện.", + "ENABLED": "Bật", + "DISABLED": "Không bật" }, "WIDGET_COLOR": { - "LABEL": "Widget Color", - "PLACEHOLDER": "Update the widget color used in widget" + "LABEL": "Màu tiện ích", + "PLACEHOLDER": "Cập nhật màu tiện ích con được sử dụng trong tiện ích con" }, - "SUBMIT_BUTTON": "Create inbox" + "SUBMIT_BUTTON": "Tạo hộp thư đến" }, "TWILIO": { - "TITLE": "Twilio SMS/Whatsapp Channel", - "DESC": "Integrate Twilio and start supporting your customers via SMS or Whatsapp.", + "TITLE": "Kênh Twilio SMS/Whatsapp", + "DESC": "Tích hợp Twilio và bắt đầu hỗ trợ khách hàng của bạn qua SMS hoặc Whatsapp.", "ACCOUNT_SID": { - "LABEL": "Account SID", - "PLACEHOLDER": "Please enter your Twilio Account SID", - "ERROR": "This field is required" + "LABEL": "Tài khoản SID", + "PLACEHOLDER": "Vui lòng nhập SID tài khoản Twilio của bạn", + "ERROR": "Trường này là bắt buộc" }, "CHANNEL_TYPE": { - "LABEL": "Channel Type", - "ERROR": "Please select your Channel Type" + "LABEL": "Loại kênh", + "ERROR": "Vui lòng chọn loại kênh của bạn" }, "AUTH_TOKEN": { "LABEL": "Auth Token", - "PLACEHOLDER": "Please enter your Twilio Auth Token", - "ERROR": "This field is required" + "PLACEHOLDER": "Vui lòng nhập Twilio Auth Token", + "ERROR": "Trường này là bắt buộc" }, "CHANNEL_NAME": { - "LABEL": "Channel Name", - "PLACEHOLDER": "Please enter a channel name", - "ERROR": "This field is required" + "LABEL": "Tên kênh", + "PLACEHOLDER": "Vui lòng nhập tên kênh", + "ERROR": "Trường này là bắt buộc" }, "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." + "LABEL": "Số Điện Thoại", + "PLACEHOLDER": "Vui lòng nhập số điện thoại mà tin nhắn sẽ được gửi.", + "ERROR": "Vui lòng nhập một giá trị hợp lệ. Số điện thoại phải bắt đầu bằng `+`." }, "API_CALLBACK": { "TITLE": "Callback URL", - "SUBTITLE": "You have to configure the message callback URL in Twilio with the URL mentioned here." + "SUBTITLE": "Bạn phải định cấu hình URL gọi lại tin nhắn trong Twilio với URL được đề cập ở đây." }, - "SUBMIT_BUTTON": "Create Twilio Channel", + "SUBMIT_BUTTON": "Tạo kênh Twilio", "API": { - "ERROR_MESSAGE": "We were not able to authenticate Twilio credentials, please try again" + "ERROR_MESSAGE": "Chúng tôi không thể xác thực thông tin đăng nhập Twilio, vui lòng thử lại" } }, "API_CHANNEL": { - "TITLE": "API Channel", - "DESC": "Integrate with API channel and start supporting your customers.", + "TITLE": "Kênh API", + "DESC": "Tích hợp với kênh API và bắt đầu hỗ trợ khách hàng của bạn.", "CHANNEL_NAME": { - "LABEL": "Channel Name", - "PLACEHOLDER": "Please enter a channel name", - "ERROR": "This field is required" + "LABEL": "Tên Kênh", + "PLACEHOLDER": "Vui lòng nhập tên kênh", + "ERROR": "Trường này là bắt buộc" }, "WEBHOOK_URL": { "LABEL": "Webhook URL", - "SUBTITLE": "Configure the URL where you want to recieve callbacks on events.", + "SUBTITLE": "Định cấu hình URL mà bạn muốn nhận các cuộc gọi lại trên các events.", "PLACEHOLDER": "Webhook URL" }, - "SUBMIT_BUTTON": "Create API Channel", + "SUBMIT_BUTTON": "Tạo kênh API", "API": { - "ERROR_MESSAGE": "We were not able to save the api channel" + "ERROR_MESSAGE": "Chúng tôi không thể lưu kênh api" } }, "EMAIL_CHANNEL": { - "TITLE": "Email Channel", - "DESC": "Integrate you email inbox.", + "TITLE": "Kênh Email", + "DESC": "Tích hợp hộp thư đến email của bạn.", "CHANNEL_NAME": { - "LABEL": "Channel Name", - "PLACEHOLDER": "Please enter a channel name", - "ERROR": "This field is required" + "LABEL": "Tên kênh", + "PLACEHOLDER": "Vui lòng nhập tên kênh", + "ERROR": "Trường này là bắt buộc" }, "EMAIL": { "LABEL": "Email", - "SUBTITLE": "Email where your customers sends you support tickets", + "SUBTITLE": "Email nơi khách hàng của bạn gửi cho bạn vé hỗ trợ", "PLACEHOLDER": "Email" }, - "SUBMIT_BUTTON": "Create Email Channel", + "SUBMIT_BUTTON": "Tạo kênh Email", "API": { - "ERROR_MESSAGE": "We were not able to save the email channel" + "ERROR_MESSAGE": "Chúng tôi không thể lưu kênh email" }, - "FINISH_MESSAGE": "Start forwarding your emails to the following email address." + "FINISH_MESSAGE": "Bắt đầu chuyển tiếp email của bạn tới địa chỉ email sau." }, "AUTH": { - "TITLE": "Channels", - "DESC": "Currently we support Website live chat widgets, Facebook Pages and Twitter profiles as platforms. We have more platforms like Whatsapp, Email, Telegram and Line in the works, which will be out soon." + "TITLE": "Kênh", + "DESC": "Hiện tại, chúng tôi hỗ trợ các tiện ích trò chuyện trực tiếp trên Trang web, Trang Facebook và hồ sơ Twitter làm nền tảng. Chúng tôi có nhiều nền tảng hơn như Whatsapp, Email, Telegram và Line đang hoạt động, sẽ sớm ra mắt." }, "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.
PS: As an administrator, if you need access to all inboxes, you should add yourself as agent to all inboxes that you create." + "TITLE": "Nhà Cung Cấp", + "DESC": "Tại đây bạn có thể thêm các tác nhân để quản lý hộp thư đến mới tạo của mình. Chỉ những đại lý được chọn này mới có quyền truy cập vào hộp thư đến của bạn. Các nhân viên không thuộc hộp thư đến này sẽ không thể xem hoặc trả lời thư trong hộp thư đến này khi họ đăng nhập.
PS: Với tư cách là quản trị viên, nếu bạn cần quyền truy cập vào tất cả các hộp thư đến, bạn nên thêm mình làm đại lý cho tất cả các hộp thư đến mà bạn tạo." }, "DETAILS": { - "TITLE": "Inbox Details", - "DESC": "From the dropdown below, select the Facebook Page you want to connect to Chatwoot. You can also give a custom name to your inbox for better identification." + "TITLE": "Chi tiết Hộp thư đến", + "DESC": "Từ menu thả xuống bên dưới, chọn Trang Facebook bạn muốn kết nối với Chatwoot. Bạn cũng có thể đặt tên tùy chỉnh cho hộp thư đến của mình để nhận dạng tốt hơn." }, "FINISH": { "TITLE": "Nailed It!", - "DESC": "You have successfully finished integrating your Facebook Page with Chatwoot. Next time a customer messages your Page, the conversation will automatically appear on your inbox.
We are also providing you with a widget script that you can easily add to your website. Once this is live on your website, customers can message you right from your website without the help of any external tool and the conversation will appear right here, on Chatwoot.
Cool, huh? Well, we sure try to be :)" + "DESC": "Bạn đã tích hợp thành công Trang Facebook của mình với Chatwoot. Lần tới khi khách hàng nhắn tin cho Trang của bạn, cuộc trò chuyện sẽ tự động xuất hiện trong hộp thư đến của bạn.
Chúng tôi cũng đang cung cấp cho bạn tập lệnh widget mà bạn có thể dễ dàng thêm vào trang web của mình. Khi điều này xuất hiện trên trang web của bạn, khách hàng có thể nhắn tin cho bạn ngay từ trang web của bạn mà không cần sự trợ giúp của bất kỳ công cụ bên ngoài nào và cuộc trò chuyện sẽ xuất hiện ngay tại đây, trên Chatwoot.
Cool, huh? Well :)" } }, "DETAILS": { - "LOADING_FB": "Authenticating you with Facebook...", - "ERROR_FB_AUTH": "Something went wrong, Please refresh page...", - "CREATING_CHANNEL": "Creating your Inbox...", - "TITLE": "Configure Inbox Details", + "LOADING_FB": "Xác thực bạn bằng Facebook...", + "ERROR_FB_AUTH": "Đã xảy ra sự cố, Vui lòng làm mới trang...", + "CREATING_CHANNEL": "Tạo Hộp thư đến của bạn...", + "TITLE": "Cấu hình chi tiết hộp thư đến", "DESC": "" }, "AGENTS": { - "BUTTON_TEXT": "Add agents", - "ADD_AGENTS": "Adding Agents to your Inbox..." + "BUTTON_TEXT": "Thêm các nhà cung cấp", + "ADD_AGENTS": "Thêm các nhà cung cấp vào hộp thư đến của bạn..." }, "FINISH": { - "TITLE": "Your Inbox is ready!", - "MESSAGE": "You can now engage with your customers through your new Channel. Happy supporting ", - "BUTTON_TEXT": "Take me there", - "WEBSITE_SUCCESS": "You have successfully finished creating a website channel. Copy the code shown below and paste it on your website. Next time a customer use the live chat, the conversation will automatically appear on your inbox." + "TITLE": "Hộp thư đến của bạn đã sẵn sàng!", + "MESSAGE": "Giờ đây, bạn có thể tương tác với khách hàng thông qua Kênh mới của mình. Chúc vui vẻ ủng hộ ", + "BUTTON_TEXT": "Đưa cho tôi", + "WEBSITE_SUCCESS": "Bạn đã hoàn thành việc tạo kênh trang web thành công. Sao chép mã được hiển thị bên dưới và dán vào trang web của bạn. Lần tới khi khách hàng sử dụng cuộc trò chuyện trực tiếp, cuộc trò chuyện sẽ tự động xuất hiện trong hộp thư đến của bạn." }, - "REAUTH": "Reauthorize", - "VIEW": "View", + "REAUTH": "Ủy quyền lại", + "VIEW": "Xem", "EDIT": { "API": { - "SUCCESS_MESSAGE": "Inbox settings updated successfully", - "AUTO_ASSIGNMENT_SUCCESS_MESSAGE": "Auto assignment updated successfully", - "ERROR_MESSAGE": "Could not update widget color. Please try again later." + "SUCCESS_MESSAGE": "Đã cập nhật cài đặt hộp thư đến thành công", + "AUTO_ASSIGNMENT_SUCCESS_MESSAGE": "Đã cập nhật thành công bài tập tự động", + "ERROR_MESSAGE": "Không thể cập nhật màu tiện ích. Vui lòng thử lại sau." }, "AUTO_ASSIGNMENT": { - "ENABLED": "Enabled", - "DISABLED": "Disabled" + "ENABLED": "Bật", + "DISABLED": "Không bật" } }, "DELETE": { - "BUTTON_TEXT": "Delete", + "BUTTON_TEXT": "Xoá", "CONFIRM": { - "TITLE": "Confirm Deletion", - "MESSAGE": "Are you sure to delete ", - "YES": "Yes, Delete ", - "NO": "No, Keep " + "TITLE": "Xác nhận xoá", + "MESSAGE": "Bạn có muốn xoá? ", + "YES": "Có, Xoá ", + "NO": "Không, Giữ " }, "API": { - "SUCCESS_MESSAGE": "Inbox deleted successfully", - "ERROR_MESSAGE": "Could not delete inbox. Please try again later." + "SUCCESS_MESSAGE": "Hộp thư đến đã được xóa thành công", + "ERROR_MESSAGE": "Không thể xóa hộp thư đến. Vui lòng thử lại sau." } }, "TABS": { - "SETTINGS": "Settings", - "COLLABORATORS": "Collaborators", - "CONFIGURATION": "Configuration" + "SETTINGS": "Cài đặt", + "COLLABORATORS": "Cộng tác viên", + "CONFIGURATION": "Cấu hình" }, - "SETTINGS": "Settings", + "SETTINGS": "Cài đặt", "FEATURES": { - "LABEL": "Features", - "DISPLAY_FILE_PICKER": "Display file picker on the widget", - "DISPLAY_EMOJI_PICKER": "Display emoji picker on the widget" + "LABEL": "Các tính năng", + "DISPLAY_FILE_PICKER": "Hiển thị bộ chọn tệp trên tiện ích con", + "DISPLAY_EMOJI_PICKER": "Hiển thị bộ chọn biểu tượng cảm xúc trên tiện ích con" }, "SETTINGS_POPUP": { - "MESSENGER_HEADING": "Messenger Script", - "MESSENGER_SUB_HEAD": "Place this button inside your body tag", - "INBOX_AGENTS": "Agents", - "INBOX_AGENTS_SUB_TEXT": "Add or remove agents from this inbox", - "UPDATE": "Update", - "AUTO_ASSIGNMENT": "Enable auto assignment", - "INBOX_UPDATE_TITLE": "Inbox Settings", - "INBOX_UPDATE_SUB_TEXT": "Update your inbox settings", - "AUTO_ASSIGNMENT_SUB_TEXT": "Enable or disable the automatic assignment of new conversations to the agents added to this inbox." + "MESSENGER_HEADING": "Tập lệnh Messenger", + "MESSENGER_SUB_HEAD": "Đặt nút này bên trong thẻ body của bạn", + "INBOX_AGENTS": "Nhà cung cấp", + "INBOX_AGENTS_SUB_TEXT": "Thêm hoặc xóa tác nhân khỏi hộp thư đến này", + "UPDATE": "Cập nhật", + "AUTO_ASSIGNMENT": "Bật tự động chuyển nhượng", + "INBOX_UPDATE_TITLE": "Cài đặt Hộp thư đến", + "INBOX_UPDATE_SUB_TEXT": "Cập nhật cài đặt hộp thư đến của bạn", + "AUTO_ASSIGNMENT_SUB_TEXT": "Bật hoặc tắt tính năng tự động gán các cuộc hội thoại mới cho các tác nhân được thêm vào hộp thư đến này." } } } diff --git a/app/javascript/dashboard/i18n/locale/vi/integrations.json b/app/javascript/dashboard/i18n/locale/vi/integrations.json index 17952efd1..bab577dd5 100644 --- a/app/javascript/dashboard/i18n/locale/vi/integrations.json +++ b/app/javascript/dashboard/i18n/locale/vi/integrations.json @@ -1,63 +1,63 @@ { "INTEGRATION_SETTINGS": { - "HEADER": "Integrations", + "HEADER": "Tích hợp", "WEBHOOK": { "TITLE": "Webhook", - "CONFIGURE": "Configure", - "HEADER": "Webhook settings", - "HEADER_BTN_TXT": "Add new webhook", - "INTEGRATION_TXT": "Webhook events provide you the realtime information about what's happening in your Chatwoot account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks.", - "LOADING": "Fetching attached webhooks", - "SEARCH_404": "There are no items matching this query", - "SIDEBAR_TXT": "

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.

", - "LIST": { - "404": "There are no webhooks configured for this account.", - "TITLE": "Manage webhooks", - "DESC": "Webhooks are predefined reply templates which can be used to quickly send out replies to tickets.", + "CONFIGURE": "Cấu hình", + "HEADER": "Cài đặt Webhook", + "HEADER_BTN_TXT": "Thêm mới webhook", + "INTEGRATION_TXT": "Webhook events cung cấp cho bạn thông tin thời gian thực về những gì đang xảy ra trong tài khoản Chatwoot của bạn. Bạn có thể sử dụng webhook để truyền tải các sự kiện tới các ứng dụng yêu thích của mình như Slack hoặc Github. Nhấp vào Định cấu hình để thiết lập webhook của bạn..", + "LOADING": "Đang tải về các webhooks", + "SEARCH_404": "Không có kết quả nào được tìm thấy", + "SIDEBAR_TXT": "

Webhooks

Webhook là các lệnh gọi lại HTTP có thể được xác định cho mọi tài khoản. Chúng được kích hoạt bởi các sự kiện như tạo tin nhắn trong Chatwoot. Bạn có thể tạo nhiều webhook cho tài khoản này.

Để tạo một webhook, click vào Tạo mới. Bạn cũng có thể xóa bất kỳ webhook hiện có nào bằng cách nhấp vào nút Xóa.

", + "LIST": { + "404": "Không có webhook nào được định cấu hình cho tài khoản này.", + "TITLE": "Quản lý webhooks", + "DESC": "Webhook là các mẫu trả lời được xác định trước có thể được sử dụng để nhanh chóng gửi câu trả lời cho vé", "TABLE_HEADER": [ "Webhook endpoint", - "Actions" + "Hành động" ] }, "ADD": { - "CANCEL": "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.", + "CANCEL": "Xoá", + "TITLE": "Thêm mới webhook", + "DESC": "Webhook events cung cấp cho bạn thông tin thời gian thực về những gì đang xảy ra trong tài khoản Chatwoot của bạn. Vui lòng nhập một URL hợp lệ để định cấu hình một cuộc gọi lại.", "FORM": { "END_POINT": { "LABEL": "Webhook URL", - "PLACEHOLDER": "Example: https://example/api/webhook", - "ERROR": "Please enter a valid URL" + "PLACEHOLDER": "Ví dụ: https://example/api/webhook", + "ERROR": "Vui lòng nhập một URL hợp lệ" }, - "SUBMIT": "Create webhook" + "SUBMIT": "Tạo webhook" }, "API": { - "SUCCESS_MESSAGE": "Webhook added successfully", - "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + "SUCCESS_MESSAGE": "Webhook đã được thêm thành công", + "ERROR_MESSAGE": "Không thể kết nối với Máy chủ Woot, Vui lòng thử lại sau" } }, "DELETE": { - "BUTTON_TEXT": "Delete", + "BUTTON_TEXT": "Xoá", "API": { - "SUCCESS_MESSAGE": "Webhook deleted successfully", - "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + "SUCCESS_MESSAGE": "Webhook đã được xoá thành công", + "ERROR_MESSAGE": "Không thể kết nối với Máy chủ Woot, Vui lòng thử lại sau" }, "CONFIRM": { - "TITLE": "Confirm Deletion", - "MESSAGE": "Are you sure to delete ", - "YES": "Yes, Delete ", - "NO": "No, Keep it" + "TITLE": "Xác Nhận Xoá", + "MESSAGE": "Bạn có chắc muốn xoá? ", + "YES": "Có, Xoá ", + "NO": "Không, Giữ" } } }, "DELETE": { - "BUTTON_TEXT": "Delete", + "BUTTON_TEXT": "Xoá", "API": { - "SUCCESS_MESSAGE": "Integration deleted successfully" + "SUCCESS_MESSAGE": "Tích hợp đã được xóa thành công" } }, "CONNECT": { - "BUTTON_TEXT": "Connect" + "BUTTON_TEXT": "Kết nối" } } } diff --git a/app/javascript/dashboard/i18n/locale/vi/labelsMgmt.json b/app/javascript/dashboard/i18n/locale/vi/labelsMgmt.json index b0dbe439f..e63625def 100644 --- a/app/javascript/dashboard/i18n/locale/vi/labelsMgmt.json +++ b/app/javascript/dashboard/i18n/locale/vi/labelsMgmt.json @@ -1,67 +1,67 @@ { "LABEL_MGMT": { - "HEADER": "Labels", - "HEADER_BTN_TXT": "Add label", - "LOADING": "Fetching labels", - "SEARCH_404": "There are no items matching this query", - "SIDEBAR_TXT": "

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.

", + "HEADER": "Nhãn", + "HEADER_BTN_TXT": "Thêm nhãn", + "LOADING": "Tải về các nhãn", + "SEARCH_404": "Không có kết quả nào phù hợp", + "SIDEBAR_TXT": "

Nhãn

Nhãn giúp bạn phân loại các cuộc hội thoại và ưu tiên chúng. Bạn có thể gán nhãn cho một cuộc hội thoại từ bảng phụ.

Nhãn được liên kết với tài khoản và có thể được sử dụng để tạo quy trình công việc tùy chỉnh trong tổ chức của bạn. Bạn có thể gán màu tùy chỉnh cho nhãn, điều này giúp bạn nhận dạng nhãn dễ dàng hơn. Bạn sẽ có thể hiển thị nhãn trên thanh bên để lọc các cuộc hội thoại một cách dễ dàng.

", "LIST": { - "404": "There are no labels available in this account.", - "TITLE": "Manage labels", - "DESC": "Labels let you group the conversations together.", + "404": "Không có nhãn nào có sẵn trong tài khoản này.", + "TITLE": "Quản lý nhãn", + "DESC": "Nhãn cho phép bạn nhóm các cuộc trò chuyện lại với nhau.", "TABLE_HEADER": [ - "Name", - "Description", - "Color" + "Tên", + "Mô tả", + "Màu sắc" ] }, "FORM": { "NAME": { - "LABEL": "Label Name", - "PLACEHOLDER": "Label name", - "ERROR": "Label Name is required" + "LABEL": "Tên nhãn", + "PLACEHOLDER": "Tên nhãn", + "ERROR": "Tên nhãn là bắt buộc" }, "DESCRIPTION": { - "LABEL": "Description", - "PLACEHOLDER": "Label Description" + "LABEL": "Mô tả", + "PLACEHOLDER": "Mô tả cho nhãn" }, "COLOR": { - "LABEL": "Color" + "LABEL": "Màu sắc" }, "SHOW_ON_SIDEBAR": { - "LABEL": "Show label on sidebar" + "LABEL": "Hiển thị nhãn trên sidebar" }, - "EDIT": "Edit", - "CREATE": "Create", - "DELETE": "Delete", - "CANCEL": "Cancel" + "EDIT": "Chỉnh sửa", + "CREATE": "Tạo", + "DELETE": "Xoá", + "CANCEL": "Huỷ" }, "ADD": { - "TITLE": "Add label", - "DESC": "Labels let you group the conversations together.", + "TITLE": "Thêm nhãn", + "DESC": "Nhãn cho phép bạn nhóm các cuộc trò chuyện lại với nhau.", "API": { - "SUCCESS_MESSAGE": "Label added successfully", - "ERROR_MESSAGE": "There was an error, please try again" + "SUCCESS_MESSAGE": "Thêm nhãn thành công", + "ERROR_MESSAGE": "Đã có lỗi, vui lòng thử lại" } }, "EDIT": { - "TITLE": "Edit label", + "TITLE": "Chỉnh sửa nhãn", "API": { - "SUCCESS_MESSAGE": "Label updated successfully", - "ERROR_MESSAGE": "There was an error, please try again" + "SUCCESS_MESSAGE": "Nhãn được cập nhật thành công", + "ERROR_MESSAGE": "Đã có lỗi, vui lòng thử lại" } }, "DELETE": { - "BUTTON_TEXT": "Delete", + "BUTTON_TEXT": "Xoá", "API": { - "SUCCESS_MESSAGE": "Label deleted successfully", - "ERROR_MESSAGE": "There was an error, please try again" + "SUCCESS_MESSAGE": "Nhãn đã được xoá thành công", + "ERROR_MESSAGE": "Đã có lỗi, vui lòng thử lại" }, "CONFIRM": { - "TITLE": "Confirm Deletion", - "MESSAGE": "Are you sure to delete ", - "YES": "Yes, Delete ", - "NO": "No, Keep " + "TITLE": "Xác Nhận Xoá", + "MESSAGE": "Bạn có chắc muốn xoá? ", + "YES": "Có, xoá ", + "NO": "Không, giữ " } } } diff --git a/app/javascript/dashboard/i18n/locale/vi/login.json b/app/javascript/dashboard/i18n/locale/vi/login.json index 30f667052..d1d05dc31 100644 --- a/app/javascript/dashboard/i18n/locale/vi/login.json +++ b/app/javascript/dashboard/i18n/locale/vi/login.json @@ -1,21 +1,21 @@ { "LOGIN": { - "TITLE": "Login to Chatwoot", + "TITLE": "Đăng nhập Chatwoot", "EMAIL": { "LABEL": "Email", - "PLACEHOLDER": "Email eg: someone@example.com" + "PLACEHOLDER": "Email : someone@example.com" }, "PASSWORD": { - "LABEL": "Password", - "PLACEHOLDER": "Password" + "LABEL": "Mật khẩu", + "PLACEHOLDER": "Mật khẩu" }, "API": { - "SUCCESS_MESSAGE": "Login Successful", - "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later", - "UNAUTH": "Username / Password Incorrect. Please try again" + "SUCCESS_MESSAGE": "Đăng nhập thành công", + "ERROR_MESSAGE": "Không thể kết nối với Máy chủ Woot, Vui lòng thử lại sau", + "UNAUTH": "Tài khoản/Mật khẩu không đúng. Vui lòng thử lại" }, - "FORGOT_PASSWORD": "Forgot your password?", - "CREATE_NEW_ACCOUNT": "Create new account", - "SUBMIT": "Login" + "FORGOT_PASSWORD": "Quên mật khẩu?", + "CREATE_NEW_ACCOUNT": "Tạo mới tài khoản", + "SUBMIT": "Đăng nhập" } } diff --git a/app/javascript/dashboard/i18n/locale/vi/report.json b/app/javascript/dashboard/i18n/locale/vi/report.json index 8a4d53485..16d1a927f 100644 --- a/app/javascript/dashboard/i18n/locale/vi/report.json +++ b/app/javascript/dashboard/i18n/locale/vi/report.json @@ -1,42 +1,42 @@ { "REPORT": { - "HEADER": "Reports", - "LOADING_CHART": "Loading chart data...", - "NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.", + "HEADER": "Báo cáo", + "LOADING_CHART": "Đang tải các biểu đồ dữ liệu...", + "NO_ENOUGH_DATA": "Chúng tôi không nhận được đủ điểm dữ liệu để tạo báo cáo, Vui lòng thử lại sau.", "METRICS": { "CONVERSATIONS": { - "NAME": "Conversations", - "DESC": "( Total )" + "NAME": "Các cuộc trò chuyện", + "DESC": "( Tổng cộng )" }, "INCOMING_MESSAGES": { - "NAME": "Incoming Messages", - "DESC": "( Total )" + "NAME": "Tin nhắn nhận được", + "DESC": "( Tổng cộng )" }, "OUTGOING_MESSAGES": { - "NAME": "Outgoing Messages", - "DESC": "( Total )" + "NAME": "Tin nhắn gửi đi", + "DESC": "( Tổng cộng )" }, "FIRST_RESPONSE_TIME": { - "NAME": "First response time", - "DESC": "( Avg )" + "NAME": "Thời gian phản hồi đầu tiên", + "DESC": "( Trung bình )" }, "RESOLUTION_TIME": { - "NAME": "Resolution Time", - "DESC": "( Avg )" + "NAME": "Thời gian giải quyết", + "DESC": "( Trung bình )" }, "RESOLUTION_COUNT": { - "NAME": "Resolution Count", - "DESC": "( Total )" + "NAME": "Số lượng giải quyết", + "DESC": "( Tổng cộng )" } }, "DATE_RANGE": [ { "id": 0, - "name": "Last 7 days" + "name": "7 ngày cuối" }, { "id": 1, - "name": "Last 30 days" + "name": "30 ngày cuối" } ] } diff --git a/app/javascript/dashboard/i18n/locale/vi/resetPassword.json b/app/javascript/dashboard/i18n/locale/vi/resetPassword.json index bb678e809..2df7d6592 100644 --- a/app/javascript/dashboard/i18n/locale/vi/resetPassword.json +++ b/app/javascript/dashboard/i18n/locale/vi/resetPassword.json @@ -1,15 +1,15 @@ { "RESET_PASSWORD": { - "TITLE": "Reset Password", + "TITLE": "Đặt lại mật khẩu", "EMAIL": { "LABEL": "Email", - "PLACEHOLDER": "Please enter your email", - "ERROR": "Please enter a valid email" + "PLACEHOLDER": "Vui lòng nhập email", + "ERROR": "Vui lòng nhập email hợp lệ" }, "API": { - "SUCCESS_MESSAGE": "Password reset link has been sent to your email", - "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + "SUCCESS_MESSAGE": "Liên kết đặt lại mật khẩu đã được gửi đến email của bạn", + "ERROR_MESSAGE": "Không thể kết nối với Máy chủ Woot, Vui lòng thử lại sau" }, - "SUBMIT": "Submit" + "SUBMIT": "Gửi" } } diff --git a/app/javascript/dashboard/i18n/locale/vi/setNewPassword.json b/app/javascript/dashboard/i18n/locale/vi/setNewPassword.json index 94a3fd2e1..23ad19b54 100644 --- a/app/javascript/dashboard/i18n/locale/vi/setNewPassword.json +++ b/app/javascript/dashboard/i18n/locale/vi/setNewPassword.json @@ -1,20 +1,20 @@ { "SET_NEW_PASSWORD": { - "TITLE": "Set New Password", + "TITLE": "Cài đặt mật khẩu mới", "PASSWORD": { - "LABEL": "Password", - "PLACEHOLDER": "Password", - "ERROR": "Password is too short" + "LABEL": "Mật khẩu", + "PLACEHOLDER": "Mật khẩu", + "ERROR": "Mật khẩu quá ngắn" }, "CONFIRM_PASSWORD": { - "LABEL": "Confirm Password", - "PLACEHOLDER": "Confirm Password", - "ERROR": "Passwords do not match" + "LABEL": "Xác nhận mật khẩu", + "PLACEHOLDER": "Xác nhận mật khẩu", + "ERROR": "Mật khẩu không khớp" }, "API": { - "SUCCESS_MESSAGE": "Successfully changed the password", - "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + "SUCCESS_MESSAGE": "Đổi mật khẩu thành công", + "ERROR_MESSAGE": "Không thể kết nối với Máy chủ Woot, Vui lòng thử lại sau" }, - "SUBMIT": "Submit" + "SUBMIT": "Gửi" } } diff --git a/app/javascript/dashboard/i18n/locale/vi/settings.json b/app/javascript/dashboard/i18n/locale/vi/settings.json index f300f5fa3..0a6be1f87 100644 --- a/app/javascript/dashboard/i18n/locale/vi/settings.json +++ b/app/javascript/dashboard/i18n/locale/vi/settings.json @@ -1,148 +1,148 @@ { "PROFILE_SETTINGS": { - "LINK": "Profile Settings", - "TITLE": "Profile Settings", - "BTN_TEXT": "Update Profile", - "AFTER_EMAIL_CHANGED": "Your profile has been updated successfully, please login again as your login credentials are changed", + "LINK": "Cài Đặt Hồ Sơ", + "TITLE": "Cài Đặt Hồ Sơ", + "BTN_TEXT": "Cập Nhật Hồ Sơ", + "AFTER_EMAIL_CHANGED": "Hồ sơ của bạn đã được cập nhật thành công, vui lòng đăng nhập lại khi thông tin đăng nhập của bạn được thay đổi", "FORM": { - "AVATAR": "Profile Image", - "ERROR": "Please fix form errors", - "REMOVE_IMAGE": "Remove", - "UPLOAD_IMAGE": "Upload image", - "UPDATE_IMAGE": "Update image", + "AVATAR": "Hình ảnh hồ sơ cá nhân", + "ERROR": "Vui lòng sửa lỗi biểu mẫu", + "REMOVE_IMAGE": "Xoá", + "UPLOAD_IMAGE": "Tải lên hình ảnh", + "UPDATE_IMAGE": "Cập nhật hình ảnh", "PROFILE_SECTION": { - "TITLE": "Profile", - "NOTE": "Your email address is your identity and is used to log in." + "TITLE": "Hồ sơ cá nhân", + "NOTE": "Địa chỉ email của bạn là danh tính của bạn và được sử dụng để đăng nhập." }, "PASSWORD_SECTION": { - "TITLE": "Password", - "NOTE": "Updating your password would reset your logins in multiple devices." + "TITLE": "Mật khẩu", + "NOTE": "Cập nhật mật khẩu của bạn sẽ đặt lại thông tin đăng nhập của bạn trên nhiều thiết bị." }, "ACCESS_TOKEN": { "TITLE": "Access Token", - "NOTE": "This token can be used if you are building an API based integration" + "NOTE": "Có thể sử dụng Token này nếu bạn đang xây dựng tích hợp dựa trên API" }, "EMAIL_NOTIFICATIONS_SECTION": { - "TITLE": "Email Notifications", - "NOTE": "Update your email notification preferences here", - "CONVERSATION_ASSIGNMENT": "Send email notifications when a conversation is assigned to me", - "CONVERSATION_CREATION": "Send email notifications when a new conversation is created" + "TITLE": "Thông Báo Email", + "NOTE": "Cập nhật tùy chọn thông báo qua email của bạn tại đây", + "CONVERSATION_ASSIGNMENT": "Gửi thông báo qua email khi cuộc trò chuyện được chỉ định cho tôi", + "CONVERSATION_CREATION": "Gửi thông báo qua email khi cuộc trò chuyện mới được tạo" }, "API": { - "UPDATE_SUCCESS": "Your notification preferences are updated successfully", - "UPDATE_ERROR": "There is an error while updating the preferences, please try again" + "UPDATE_SUCCESS": "Tùy chọn thông báo của bạn đã được cập nhật thành công", + "UPDATE_ERROR": "Đã xảy ra lỗi khi cập nhật tùy chọn, vui lòng thử lại" }, "PUSH_NOTIFICATIONS_SECTION": { - "TITLE": "Push Notifications", - "NOTE": "Update your push notification preferences here", - "CONVERSATION_ASSIGNMENT": "Send push notifications when a conversation is assigned to me", - "CONVERSATION_CREATION": "Send push notifications when a new conversation is created", - "HAS_ENABLED_PUSH": "You have enabled push for this browser.", - "REQUEST_PUSH": "Enable push notifications" + "TITLE": "Thông Báo", + "NOTE": "Cập nhật tùy chọn thông báo của bạn tại đây", + "CONVERSATION_ASSIGNMENT": "Gửi thông báo khi cuộc trò chuyện được chỉ định cho tôi", + "CONVERSATION_CREATION": "Gửi thông báo khi cuộc trò chuyện mới được tạo", + "HAS_ENABLED_PUSH": "Bạn đã bật tính năng thông báo cho trình duyệt này.", + "REQUEST_PUSH": "Bật thông báo" }, "PROFILE_IMAGE": { - "LABEL": "Profile Image" + "LABEL": "Hình ảnh hồ sơ cá nhân" }, "NAME": { - "LABEL": "Your full name", - "ERROR": "Please enter a valid full name", - "PLACEHOLDER": "Please enter your full name" + "LABEL": "Tên đầy đủ của bạn", + "ERROR": "Vui lòng nhập tên đầy đủ hợp lệ", + "PLACEHOLDER": "Vui lòng nhập tên đầy đủ của bạn" }, "DISPLAY_NAME": { - "LABEL": "Display name", - "ERROR": "Please enter a valid display name", - "PLACEHOLDER": "Please enter a display name, this would be displayed in conversations" + "LABEL": "Tên hiển thị", + "ERROR": "Vui lòng nhập tên hiển thị hợp lệ", + "PLACEHOLDER": "Vui lòng nhập tên hiển thị, tên này sẽ được hiển thị trong các cuộc trò chuyện" }, "AVAILABILITY": { - "LABEL": "Availability", + "LABEL": "Khả dụng", "STATUSES_LIST": [ { "value": "online", - "label": "Online" + "label": "Trực Tuyến" }, { "value": "busy", - "label": "Busy" + "label": "Bận" }, { "value": "offline", - "label": "Offline" + "label": "Không Trực Tuyến" } ] }, "EMAIL": { - "LABEL": "Your email address", - "ERROR": "Please enter a valid email address", - "PLACEHOLDER": "Please enter your email address, this would be displayed in conversations" + "LABEL": "Địa chỉ email của bạn", + "ERROR": "Vui lòng nhập một địa chỉ email hợp lệ", + "PLACEHOLDER": "Vui lòng nhập địa chỉ email của bạn, địa chỉ này sẽ được hiển thị trong các cuộc trò chuyện" }, "PASSWORD": { - "LABEL": "Password", - "ERROR": "Please enter a password of length 6 or more", - "PLACEHOLDER": "Please enter a new password" + "LABEL": "Mật khẩu", + "ERROR": "Vui lòng nhập mật khẩu có độ dài từ 6 trở lên", + "PLACEHOLDER": "Vui lòng nhập mật khẩu mới" }, "PASSWORD_CONFIRMATION": { - "LABEL": "Confirm new password", - "ERROR": "Confirm password should match the password", - "PLACEHOLDER": "Please re-enter your password" + "LABEL": "Xác nhận mật khẩu mới", + "ERROR": "Xác nhận mật khẩu phải khớp với mật khẩu", + "PLACEHOLDER": "Vui lòng nhập lại mật khẩu của bạn" } } }, "SIDEBAR_ITEMS": { - "CHANGE_AVAILABILITY_STATUS": "Change", - "CHANGE_ACCOUNTS": "Switch Account", - "SELECTOR_SUBTITLE": "Select an account from the following list", - "PROFILE_SETTINGS": "Profile Settings", - "LOGOUT": "Logout" + "CHANGE_AVAILABILITY_STATUS": "Đổi", + "CHANGE_ACCOUNTS": "Chuyển tài khoản", + "SELECTOR_SUBTITLE": "Chọn một tài khoản từ danh sách sau", + "PROFILE_SETTINGS": "Cài Đặt Hồ Sơ Cá Nhân", + "LOGOUT": "Đăng xuất" }, "APP_GLOBAL": { - "TRIAL_MESSAGE": "days trial remaining.", - "TRAIL_BUTTON": "Buy Now" + "TRIAL_MESSAGE": "ngày dùng thử còn lại.", + "TRAIL_BUTTON": "Mua Ngay" }, "COMPONENTS": { "CODE": { - "BUTTON_TEXT": "Copy", - "COPY_SUCCESSFUL": "Code copied to clipboard successfully" + "BUTTON_TEXT": "Sao Chép", + "COPY_SUCCESSFUL": "Đã sao chép mã thành công" }, "FILE_BUBBLE": { - "DOWNLOAD": "Download", - "UPLOADING": "Uploading..." + "DOWNLOAD": "Tải xuống", + "UPLOADING": "Đang tải lên..." }, "FORM_BUBBLE": { - "SUBMIT": "Submit" + "SUBMIT": "Gửi" } }, - "CONFIRM_EMAIL": "Verifying...", + "CONFIRM_EMAIL": "Đang xác thực...", "SETTINGS": { "INBOXES": { - "NEW_INBOX": "Add Inbox" + "NEW_INBOX": "Thêm Hộp thư đến" } }, "SIDEBAR": { - "CONVERSATIONS": "Conversations", - "REPORTS": "Reports", - "SETTINGS": "Settings", - "HOME": "Home", - "AGENTS": "Agents", - "INBOXES": "Inboxes", + "CONVERSATIONS": "Cuộc trò chuyện", + "REPORTS": "Báo cáo", + "SETTINGS": "Cài Đặt", + "HOME": "Trang Chủ", + "AGENTS": "Đại lý", + "INBOXES": "Hộp thư đến", "CANNED_RESPONSES": "Canned Responses", - "INTEGRATIONS": "Integrations", - "ACCOUNT_SETTINGS": "Account Settings", - "LABELS": "Labels" + "INTEGRATIONS": "Tích hợp", + "ACCOUNT_SETTINGS": "Cài Đặt Tài Khoản", + "LABELS": "Nhãn" }, "CREATE_ACCOUNT": { - "NEW_ACCOUNT": "New Account", - "SELECTOR_SUBTITLE": "Create a new account", + "NEW_ACCOUNT": "Tạo mới tài khoản", + "SELECTOR_SUBTITLE": "Tạo mới một tài khoản", "API": { - "SUCCESS_MESSAGE": "Account created successfully", - "EXIST_MESSAGE": "Account already exists", - "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + "SUCCESS_MESSAGE": "Tài khoản được tạo thành công", + "EXIST_MESSAGE": "Tài khoản đã tồn tại", + "ERROR_MESSAGE": "Không thể kết nối với Máy chủ Woot, Vui lòng thử lại sau" }, "FORM": { "NAME": { - "LABEL": "Account Name", + "LABEL": "Tên tài khoản", "PLACEHOLDER": "Wayne Enterprises" }, - "SUBMIT": "Submit" + "SUBMIT": "Gửi" } } } diff --git a/app/javascript/dashboard/i18n/locale/vi/signup.json b/app/javascript/dashboard/i18n/locale/vi/signup.json index e52705224..ee250f658 100644 --- a/app/javascript/dashboard/i18n/locale/vi/signup.json +++ b/app/javascript/dashboard/i18n/locale/vi/signup.json @@ -1,32 +1,32 @@ { "REGISTER": { - "TRY_WOOT": "Register an account", - "TITLE": "Register", - "TERMS_ACCEPT": "By signing up, you agree to our T & C and Privacy policy", + "TRY_WOOT": "Đăng kí tài khoản", + "TITLE": "Đăng kí", + "TERMS_ACCEPT": "Bằng cách đăng ký, bạn đồng ý với T & CChính sách bảo mật", "ACCOUNT_NAME": { - "LABEL": "Account Name", + "LABEL": "Tên Tài Khoản", "PLACEHOLDER": "Wayne Enterprises", - "ERROR": "Account Name is too short" + "ERROR": "Tên tài khoản quá ngắn" }, "EMAIL": { "LABEL": "Email", "PLACEHOLDER": "bruce@wayne.enterprises", - "ERROR": "Email is invalid" + "ERROR": "Địa chỉ email không hợp lệ" }, "PASSWORD": { - "LABEL": "Password", - "PLACEHOLDER": "Password", - "ERROR": "Password is too short" + "LABEL": "Mật khẩu", + "PLACEHOLDER": "Mật khẩu", + "ERROR": "Mật khẩu quá ngắn" }, "CONFIRM_PASSWORD": { - "LABEL": "Confirm Password", - "PLACEHOLDER": "Confirm Password", - "ERROR": "Password doesnot match" + "LABEL": "Xác nhận mật khẩu", + "PLACEHOLDER": "Xác nhận mật khẩu", + "ERROR": "Mật khẩu không khớp" }, "API": { - "SUCCESS_MESSAGE": "Registration Successfull", - "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + "SUCCESS_MESSAGE": "Đăng Kí Thành Công", + "ERROR_MESSAGE": "Không thể kết nối với Máy chủ Woot, Vui lòng thử lại sau." }, - "SUBMIT": "Submit" + "SUBMIT": "Gửi" } } diff --git a/app/javascript/dashboard/i18n/locale/vi/webhooks.json b/app/javascript/dashboard/i18n/locale/vi/webhooks.json index 347c96893..05b69aead 100644 --- a/app/javascript/dashboard/i18n/locale/vi/webhooks.json +++ b/app/javascript/dashboard/i18n/locale/vi/webhooks.json @@ -1,5 +1,5 @@ { "WEBHOOKS_SETTINGS": { - "HEADER": "Webhook Settings" + "HEADER": "Cài đặt Webhook" } } From 399f9e004a3c9bd1fe766db9f3b3156ab0ad801b Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Mon, 5 Oct 2020 22:52:43 +0530 Subject: [PATCH 35/57] fix: Use last_activity_at instead of updated_at for sorting (#1281) Co-authored-by: Akash Srivastava --- .../store/modules/conversations/index.js | 1 + app/models/conversation.rb | 3 +- app/models/message.rb | 7 ++++ .../conversations/event_data_presenter.rb | 2 +- .../conversations/search.json.jbuilder | 2 +- .../partials/_conversation.json.jbuilder | 2 +- ...22_add_last_activity_at_to_conversation.rb | 35 +++++++++++++++++++ db/schema.rb | 3 +- spec/models/conversation_spec.rb | 2 +- spec/models/message_spec.rb | 4 +++ .../event_data_presenter_spec.rb | 2 +- 11 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20200927135222_add_last_activity_at_to_conversation.rb diff --git a/app/javascript/dashboard/store/modules/conversations/index.js b/app/javascript/dashboard/store/modules/conversations/index.js index 107c94b15..8290f4f5f 100644 --- a/app/javascript/dashboard/store/modules/conversations/index.js +++ b/app/javascript/dashboard/store/modules/conversations/index.js @@ -92,6 +92,7 @@ export const mutations = { ); if (previousMessageIndex === -1) { chat.messages.push(message); + chat.timestamp = message.created_at; if (_state.selectedChatId === message.conversation_id) { window.bus.$emit('scrollToMessage'); } diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 45093a3aa..8c1099c99 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -7,6 +7,7 @@ # agent_last_seen_at :datetime # contact_last_seen_at :datetime # identifier :string +# last_activity_at :datetime not null # locked :boolean default(FALSE) # status :integer default("open"), not null # uuid :uuid not null @@ -36,7 +37,7 @@ class Conversation < ApplicationRecord enum status: { open: 0, resolved: 1, bot: 2 } - scope :latest, -> { order(updated_at: :desc) } + scope :latest, -> { order(last_activity_at: :desc) } scope :unassigned, -> { where(assignee_id: nil) } scope :assigned_to, ->(agent) { where(assignee_id: agent.id) } diff --git a/app/models/message.rb b/app/models/message.rb index f7da472c1..c1348a75d 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -129,6 +129,7 @@ class Message < ApplicationRecord def execute_after_create_commit_callbacks # rails issue with order of active record callbacks being executed # https://github.com/rails/rails/issues/20911 + set_conversation_activity dispatch_create_events send_reply execute_message_template_hooks @@ -191,4 +192,10 @@ class Message < ApplicationRecord def validate_attachments_limit(_attachment) errors.add(attachments: 'exceeded maximum allowed') if attachments.size >= NUMBER_OF_PERMITTED_ATTACHMENTS end + + def set_conversation_activity + # rubocop:disable Rails/SkipsModelValidations + conversation.update_columns(last_activity_at: created_at) + # rubocop:enable Rails/SkipsModelValidations + end end diff --git a/app/presenters/conversations/event_data_presenter.rb b/app/presenters/conversations/event_data_presenter.rb index bf09dd0b3..4a658c4c8 100644 --- a/app/presenters/conversations/event_data_presenter.rb +++ b/app/presenters/conversations/event_data_presenter.rb @@ -32,7 +32,7 @@ class Conversations::EventDataPresenter < SimpleDelegator { agent_last_seen_at: agent_last_seen_at.to_i, contact_last_seen_at: contact_last_seen_at.to_i, - timestamp: created_at.to_i + timestamp: last_activity_at.to_i } end end diff --git a/app/views/api/v1/accounts/conversations/search.json.jbuilder b/app/views/api/v1/accounts/conversations/search.json.jbuilder index e55ae8f3e..ca8bb58eb 100644 --- a/app/views/api/v1/accounts/conversations/search.json.jbuilder +++ b/app/views/api/v1/accounts/conversations/search.json.jbuilder @@ -11,7 +11,7 @@ json.data do json.status conversation.status json.muted conversation.muted? json.can_reply conversation.can_reply? - json.timestamp conversation.messages.last.try(:created_at).try(:to_i) + json.timestamp conversation.last_activity_at.to_i json.contact_last_seen_at conversation.contact_last_seen_at.to_i json.agent_last_seen_at conversation.agent_last_seen_at.to_i json.additional_attributes conversation.additional_attributes diff --git a/app/views/api/v1/conversations/partials/_conversation.json.jbuilder b/app/views/api/v1/conversations/partials/_conversation.json.jbuilder index d9068fe26..8201230fa 100644 --- a/app/views/api/v1/conversations/partials/_conversation.json.jbuilder +++ b/app/views/api/v1/conversations/partials/_conversation.json.jbuilder @@ -21,7 +21,7 @@ json.inbox_id conversation.inbox_id json.status conversation.status json.muted conversation.muted? json.can_reply conversation.can_reply? -json.timestamp conversation.messages.last.try(:created_at).try(:to_i) +json.timestamp conversation.last_activity_at.to_i json.contact_last_seen_at conversation.contact_last_seen_at.to_i json.agent_last_seen_at conversation.agent_last_seen_at.to_i json.unread_count conversation.unread_incoming_messages.count diff --git a/db/migrate/20200927135222_add_last_activity_at_to_conversation.rb b/db/migrate/20200927135222_add_last_activity_at_to_conversation.rb new file mode 100644 index 000000000..353c414cd --- /dev/null +++ b/db/migrate/20200927135222_add_last_activity_at_to_conversation.rb @@ -0,0 +1,35 @@ +class AddLastActivityAtToConversation < ActiveRecord::Migration[6.0] + def up + add_column :conversations, + :last_activity_at, + :datetime, + default: -> { 'CURRENT_TIMESTAMP' }, + index: true + + add_last_activity_at_to_conversations + + change_column_null(:conversations, :last_activity_at, false) + end + + def down + remove_column(:conversations, :last_activity_at) + end + + private + + def add_last_activity_at_to_conversations + ::Conversation.find_in_batches do |conversation_batch| + Rails.logger.info "Migrated till #{conversation_batch.first.id}\n" + conversation_batch.each do |conversation| + # rubocop:disable Rails/SkipsModelValidations + last_activity_at = if conversation.messages.last + conversation.messages.last.created_at + else + conversation.created_at + end + conversation.update_columns(last_activity_at: last_activity_at) + # rubocop:enable Rails/SkipsModelValidations + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index ae2cbf2e1..231b95f1b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_09_07_094912) do +ActiveRecord::Schema.define(version: 2020_09_27_135222) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" @@ -226,6 +226,7 @@ ActiveRecord::Schema.define(version: 2020_09_07_094912) do t.bigint "contact_inbox_id" t.uuid "uuid", default: -> { "gen_random_uuid()" }, null: false t.string "identifier" + t.datetime "last_activity_at", default: -> { "CURRENT_TIMESTAMP" }, null: false t.index ["account_id", "display_id"], name: "index_conversations_on_account_id_and_display_id", unique: true t.index ["account_id"], name: "index_conversations_on_account_id" t.index ["contact_inbox_id"], name: "index_conversations_on_contact_inbox_id" diff --git a/spec/models/conversation_spec.rb b/spec/models/conversation_spec.rb index 383bf972c..4a1642b72 100644 --- a/spec/models/conversation_spec.rb +++ b/spec/models/conversation_spec.rb @@ -370,7 +370,7 @@ RSpec.describe Conversation, type: :model do messages: [], inbox_id: conversation.inbox_id, status: conversation.status, - timestamp: conversation.created_at.to_i, + timestamp: conversation.last_activity_at.to_i, can_reply: true, channel: 'Channel::WebWidget', contact_last_seen_at: conversation.contact_last_seen_at.to_i, diff --git a/spec/models/message_spec.rb b/spec/models/message_spec.rb index b7b78da9e..290bdef05 100644 --- a/spec/models/message_spec.rb +++ b/spec/models/message_spec.rb @@ -12,6 +12,10 @@ RSpec.describe Message, type: :model do context 'when message is created' do let(:message) { build(:message, account: create(:account)) } + it 'updates conversation last_activity_at when created' do + expect(message.created_at).to eq message.conversation.last_activity_at + end + it 'triggers ::MessageTemplates::HookExecutionService' do hook_execution_service = double allow(::MessageTemplates::HookExecutionService).to receive(:new).and_return(hook_execution_service) diff --git a/spec/presenters/conversations/event_data_presenter_spec.rb b/spec/presenters/conversations/event_data_presenter_spec.rb index 430c72805..5c8cbd004 100644 --- a/spec/presenters/conversations/event_data_presenter_spec.rb +++ b/spec/presenters/conversations/event_data_presenter_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Conversations::EventDataPresenter do status: conversation.status, can_reply: conversation.can_reply?, channel: conversation.inbox.channel_type, - timestamp: conversation.created_at.to_i, + timestamp: conversation.last_activity_at.to_i, contact_last_seen_at: conversation.contact_last_seen_at.to_i, agent_last_seen_at: conversation.agent_last_seen_at.to_i, unread_count: 0 From 11725de09aced2c5b67e1a17dcf0c6a461755f6d Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Mon, 5 Oct 2020 22:53:24 +0530 Subject: [PATCH 36/57] chore: Enable Vietnamese in languages (#1314) --- config/initializers/languages.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/initializers/languages.rb b/config/initializers/languages.rb index 9d2df1c5e..c2d076dbb 100644 --- a/config/initializers/languages.rb +++ b/config/initializers/languages.rb @@ -22,7 +22,8 @@ LANGUAGES_CONFIG = { 17 => { name: 'Romanian', iso_639_3_code: 'ron', iso_639_1_code: 'ro', enabled: true }, 18 => { name: 'Tamil', iso_639_3_code: 'tam', iso_639_1_code: 'ta', enabled: true }, 19 => { name: 'Persian', iso_639_3_code: 'fas', iso_639_1_code: 'fa', enabled: true }, - 20 => { name: 'Traditional Chinese', iso_639_3_code: 'zho', iso_639_1_code: 'zh_TW', enabled: true } + 20 => { name: 'Traditional Chinese', iso_639_3_code: 'zho', iso_639_1_code: 'zh_TW', enabled: true }, + 21 => { name: 'Vietnamese', iso_639_3_code: 'vie', iso_639_1_code: 'vi', enabled: true } }.filter { |_key, val| val[:enabled] }.freeze Rails.configuration.i18n.available_locales = LANGUAGES_CONFIG.map { |_index, lang| lang[:iso_639_1_code].to_sym } From 88b2469dc891268e4cbfa0f9edcf7a08c6b040bc Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Mon, 5 Oct 2020 23:30:27 +0530 Subject: [PATCH 37/57] feat: Add API to get the active contacts (#1313) --- .../api/v1/accounts/contacts_controller.rb | 6 ++++ app/policies/contact_policy.rb | 4 +++ .../v1/accounts/contacts/active.json.jbuilder | 5 +++ config/routes.rb | 1 + lib/online_status_tracker.rb | 8 +++-- .../v1/accounts/contacts_controller_spec.rb | 35 +++++++++++++++++++ 6 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 app/views/api/v1/accounts/contacts/active.json.jbuilder diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb index 34ac91b3a..a82a30414 100644 --- a/app/controllers/api/v1/accounts/contacts_controller.rb +++ b/app/controllers/api/v1/accounts/contacts_controller.rb @@ -8,6 +8,12 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController @contacts = Current.account.contacts end + # returns online contacts + def active + @contacts = Current.account.contacts.where(id: ::OnlineStatusTracker + .get_available_contact_ids(Current.account.id)) + end + def show; end def create diff --git a/app/policies/contact_policy.rb b/app/policies/contact_policy.rb index 722e70e0b..a6e1590fb 100644 --- a/app/policies/contact_policy.rb +++ b/app/policies/contact_policy.rb @@ -3,6 +3,10 @@ class ContactPolicy < ApplicationPolicy true end + def active? + true + end + def search? true end diff --git a/app/views/api/v1/accounts/contacts/active.json.jbuilder b/app/views/api/v1/accounts/contacts/active.json.jbuilder new file mode 100644 index 000000000..b033133ed --- /dev/null +++ b/app/views/api/v1/accounts/contacts/active.json.jbuilder @@ -0,0 +1,5 @@ +json.payload do + json.array! @contacts do |contact| + json.partial! 'api/v1/models/contact.json.jbuilder', resource: contact, with_contact_inboxes: true + end +end diff --git a/config/routes.rb b/config/routes.rb index 8db4acc4a..76884e310 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -64,6 +64,7 @@ Rails.application.routes.draw do resources :contacts, only: [:index, :show, :update, :create] do collection do + get :active get :search end scope module: :contacts do diff --git a/lib/online_status_tracker.rb b/lib/online_status_tracker.rb index 622585dff..42bc0dca0 100644 --- a/lib/online_status_tracker.rb +++ b/lib/online_status_tracker.rb @@ -37,9 +37,13 @@ module OnlineStatusTracker format(::Redis::Alfred::ONLINE_STATUS, account_id: account_id) end + def self.get_available_contact_ids(account_id) + ::Redis::Alfred.zrangebyscore(presence_key(account_id, 'Contact'), (Time.zone.now - PRESENCE_DURATION).to_i, Time.now.to_i) + end + def self.get_available_contacts(account_id) - contact_ids = ::Redis::Alfred.zrangebyscore(presence_key(account_id, 'Contact'), (Time.zone.now - PRESENCE_DURATION).to_i, Time.now.to_i) - contact_ids.index_with { |_id| 'online' } + # returns {id1: 'online', id2: 'online'} + get_available_contact_ids(account_id).index_with { |_id| 'online' } end def self.get_available_users(account_id) diff --git a/spec/controllers/api/v1/accounts/contacts_controller_spec.rb b/spec/controllers/api/v1/accounts/contacts_controller_spec.rb index 04b9547bf..c2ad6fec0 100644 --- a/spec/controllers/api/v1/accounts/contacts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/contacts_controller_spec.rb @@ -30,6 +30,41 @@ RSpec.describe 'Contacts API', type: :request do end end + describe 'GET /api/v1/accounts/{account.id}/contacts/active' do + context 'when it is an unauthenticated user' do + it 'returns unauthorized' do + get "/api/v1/accounts/#{account.id}/contacts/active" + + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated user' do + let(:admin) { create(:user, account: account, role: :administrator) } + let!(:contact) { create(:contact, account: account) } + + it 'returns no contacts if no are online' do + get "/api/v1/accounts/#{account.id}/contacts/active", + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + expect(response.body).not_to include(contact.email) + end + + it 'returns all contacts who are online' do + allow(::OnlineStatusTracker).to receive(:get_available_contact_ids).and_return([contact.id]) + + get "/api/v1/accounts/#{account.id}/contacts/active", + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + expect(response.body).to include(contact.email) + end + end + end + describe 'GET /api/v1/accounts/{account.id}/contacts/search' do context 'when it is an unauthenticated user' do it 'returns unauthorized' do From db1953de24b5b9744c3190ea27b96fffe65a1ae2 Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Mon, 5 Oct 2020 23:31:23 +0530 Subject: [PATCH 38/57] fix: Add default 404 param on gravatar (#1316) --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index 45a87dac1..6bfcfdbd0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -114,7 +114,7 @@ class User < ApplicationRecord def avatar_url if avatar_img_url == '' hash = Digest::MD5.hexdigest(email) - return "https://www.gravatar.com/avatar/#{hash}" + return "https://www.gravatar.com/avatar/#{hash}?d=404" end avatar_img_url end From 2aad33a5beb72bb6c3c77802c226ebeb4c30a153 Mon Sep 17 00:00:00 2001 From: Vishal Pandey Date: Wed, 7 Oct 2020 22:02:08 +0530 Subject: [PATCH 39/57] Bugfix: Integrations page is broken on safari (#1320) Fixes #1196 --- .../dashboard/assets/scss/views/settings/integrations.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/dashboard/assets/scss/views/settings/integrations.scss b/app/javascript/dashboard/assets/scss/views/settings/integrations.scss index b43bbb4bc..7720c8d3e 100644 --- a/app/javascript/dashboard/assets/scss/views/settings/integrations.scss +++ b/app/javascript/dashboard/assets/scss/views/settings/integrations.scss @@ -8,6 +8,7 @@ .integration--image { display: flex; + height: 10rem; margin-right: $space-normal; width: 10rem; From ecebe163e10971eb235ef367fd5b814fa5e3cb5d Mon Sep 17 00:00:00 2001 From: Dmitriy Shcherbakan Date: Thu, 8 Oct 2020 09:32:08 +0300 Subject: [PATCH 40/57] feat: Ability to unmute muted conversations (#1319) --- .../v1/accounts/conversations_controller.rb | 5 + .../dashboard/api/inbox/conversation.js | 4 + .../api/specs/inbox/conversation.spec.js | 25 ++++ .../widgets/conversation/MoreActions.vue | 15 ++ .../conversation/specs/MoreActions.spec.js | 130 ++++++++++++++++++ .../dashboard/i18n/locale/en/contact.json | 2 + .../store/modules/conversations/actions.js | 9 ++ .../store/modules/conversations/index.js | 5 + .../dashboard/store/mutation-types.js | 1 + app/models/conversation.rb | 4 + config/routes.rb | 1 + .../accounts/conversations_controller_spec.rb | 25 ++++ spec/models/conversation_spec.rb | 16 +++ 13 files changed, 242 insertions(+) create mode 100644 app/javascript/dashboard/components/widgets/conversation/specs/MoreActions.spec.js diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb index e2fe7a7ed..7642e94e3 100644 --- a/app/controllers/api/v1/accounts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/conversations_controller.rb @@ -32,6 +32,11 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro head :ok end + def unmute + @conversation.unmute! + head :ok + end + def transcript ConversationReplyMailer.conversation_transcript(@conversation, params[:email])&.deliver_later if params[:email].present? head :ok diff --git a/app/javascript/dashboard/api/inbox/conversation.js b/app/javascript/dashboard/api/inbox/conversation.js index fa23fda66..6e5c72ca0 100644 --- a/app/javascript/dashboard/api/inbox/conversation.js +++ b/app/javascript/dashboard/api/inbox/conversation.js @@ -43,6 +43,10 @@ class ConversationApi extends ApiClient { return axios.post(`${this.url}/${conversationId}/mute`); } + unmute(conversationId) { + return axios.post(`${this.url}/${conversationId}/unmute`); + } + meta({ inboxId, status, assigneeType, labels }) { return axios.get(`${this.url}/meta`, { params: { diff --git a/app/javascript/dashboard/api/specs/inbox/conversation.spec.js b/app/javascript/dashboard/api/specs/inbox/conversation.spec.js index ddea6b874..31be5e87e 100644 --- a/app/javascript/dashboard/api/specs/inbox/conversation.spec.js +++ b/app/javascript/dashboard/api/specs/inbox/conversation.spec.js @@ -14,7 +14,32 @@ describe('#ConversationAPI', () => { expect(conversationAPI).toHaveProperty('markMessageRead'); expect(conversationAPI).toHaveProperty('toggleTyping'); expect(conversationAPI).toHaveProperty('mute'); + expect(conversationAPI).toHaveProperty('unmute'); expect(conversationAPI).toHaveProperty('meta'); expect(conversationAPI).toHaveProperty('sendEmailTranscript'); }); + + describe('API calls', () => { + let originalAxios = null; + let axiosMock = null; + + beforeEach(() => { + originalAxios = window.axios; + axiosMock = { post: jest.fn(() => Promise.resolve()) }; + + window.axios = axiosMock; + }); + + afterEach(() => { + window.axios = originalAxios; + }); + + it('#unmute', () => { + conversationAPI.unmute(45); + + expect(axiosMock.post).toHaveBeenCalledWith( + '/api/v1/conversations/45/unmute' + ); + }); + }); }); diff --git a/app/javascript/dashboard/components/widgets/conversation/MoreActions.vue b/app/javascript/dashboard/components/widgets/conversation/MoreActions.vue index af7217d09..f091ab323 100644 --- a/app/javascript/dashboard/components/widgets/conversation/MoreActions.vue +++ b/app/javascript/dashboard/components/widgets/conversation/MoreActions.vue @@ -22,6 +22,15 @@ > {{ $t('CONTACT_PANEL.MUTE_CONTACT') }} + + +

-
+
-
+