Merge branch 'develop' into chore/conversation-participants

This commit is contained in:
Sojan Jose 2022-10-18 00:54:01 -07:00 committed by GitHub
commit f015c006d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
434 changed files with 5292 additions and 1698 deletions

View file

@ -19,6 +19,7 @@ defaults: &defaults
- COVERAGE: true
- LOG_LEVEL: warn
parallelism: 4
resource_class: large
jobs:
build:
@ -122,9 +123,11 @@ jobs:
mkdir -p coverage
~/tmp/cc-test-reporter before-build
TESTFILES=$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
bundle exec rspec --profile 10 \
--out test-results/rspec/rspec.xml \
bundle exec rspec --format progress \
--format RspecJunitFormatter \
--out ~/tmp/test-results/rspec.xml \
-- ${TESTFILES}
no_output_timeout: 30m
- run:
name: Code Climate Test Coverage
command: |
@ -137,7 +140,7 @@ jobs:
~/tmp/cc-test-reporter before-build
TESTFILES=$(circleci tests glob **/specs/*.spec.js | circleci tests split --split-by=timings)
yarn test:coverage --profile 10 \
--out test-results/frontend_specs/rspec.xml \
--out ~/tmp/test-results/yarn.xml \
-- ${TESTFILES}
- run:
name: Code Climate Test Coverage

View file

@ -7,8 +7,8 @@ end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = spaces
indent_style = space
tab_width = 2
[{*.{rb,erb,js,coffee,json,yml,css,scss,sh,markdown,md,html}]
[*.{rb,erb,js,coffee,json,yml,css,scss,sh,markdown,md,html}]
indent_size = 2

View file

@ -58,5 +58,6 @@ jobs:
with:
context: .
file: docker/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.DOCKER_TAG }}

5
.gitignore vendored
View file

@ -39,9 +39,6 @@ public/packs*
*.un~
.jest-cache
#VS Code files
.vscode
# ignore jetbrains IDE files
.idea
@ -62,4 +59,4 @@ package-lock.json
test/cypress/videos/*
/config/master.key
/config/*.enc
/config/*.enc

View file

@ -17,6 +17,7 @@ Metrics/ClassLength:
- 'app/models/message.rb'
- 'app/builders/messages/facebook/message_builder.rb'
- 'app/controllers/api/v1/accounts/contacts_controller.rb'
- 'app/controllers/api/v1/accounts/conversations_controller.rb'
- 'app/listeners/action_cable_listener.rb'
- 'app/models/user.rb'
RSpec/ExampleLength:

32
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,32 @@
{
"recommendations": [
// Spell check
"streetsidesoftware.code-spell-checker",
// Better Comments
"aaron-bond.better-comments",
// Rails Test Runner
"davidpallinder.rails-test-runner",
// Eslint
"dbaeumer.vscode-eslint",
// Auto Close Tag
"formulahendry.auto-close-tag",
// Auto Rename Tag
"formulahendry.auto-rename-tag",
// Hight light colors
"naumovs.color-highlight",
// GitLens
"eamodio.gitlens",
// Ruby
"rebornix.ruby",
// Vue
"octref.vetur",
// Prettier
"esbenp.prettier-vscode",
// Dot Env
"mikestead.dotenv",
// HTML CSS Support
"ecmel.vscode-html-css",
// Tailwind CSS Intellisense
"bradlc.vscode-tailwindcss",
]
}

View file

@ -174,6 +174,7 @@ group :development, :test do
gem 'listen'
gem 'mock_redis'
gem 'pry-rails'
gem 'rspec_junit_formatter'
gem 'rspec-rails', '~> 5.0.0'
gem 'rubocop', require: false
gem 'rubocop-performance', require: false

View file

@ -286,9 +286,9 @@ GEM
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
google-protobuf (3.21.2)
google-protobuf (3.21.2-x86_64-darwin)
google-protobuf (3.21.2-x86_64-linux)
google-protobuf (3.21.7)
google-protobuf (3.21.7-x86_64-darwin)
google-protobuf (3.21.7-x86_64-linux)
googleapis-common-protos (1.3.12)
google-protobuf (~> 3.14)
googleapis-common-protos-types (~> 1.2)
@ -536,6 +536,8 @@ GEM
rspec-mocks (~> 3.10)
rspec-support (~> 3.10)
rspec-support (3.11.0)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.31.2)
json (~> 2.3)
parallel (~> 1.10)
@ -769,6 +771,7 @@ DEPENDENCIES
responders
rest-client
rspec-rails (~> 5.0.0)
rspec_junit_formatter
rubocop
rubocop-performance
rubocop-rails
@ -805,4 +808,4 @@ RUBY VERSION
ruby 3.0.4p208
BUNDLED WITH
2.3.17
2.3.18

View file

@ -1,13 +1,12 @@
# This Builder will create a contact inbox with specified attributes. If the contact inbox already exists, it will be returned.
# For Specific Channels like whatsapp, email etc . it smartly generated appropriate the source id when none is provided.
class ContactInboxBuilder
pattr_initialize [:contact_id!, :inbox_id!, :source_id]
pattr_initialize [:contact, :inbox, :source_id, { hmac_verified: false }]
def perform
@contact = Contact.find(contact_id)
@inbox = @contact.account.inboxes.find(inbox_id)
return unless ['Channel::TwilioSms', 'Channel::Sms', 'Channel::Email', 'Channel::Api', 'Channel::Whatsapp'].include? @inbox.channel_type
source_id = @source_id || generate_source_id
create_contact_inbox(source_id) if source_id.present?
@source_id ||= generate_source_id
create_contact_inbox if source_id.present?
end
private
@ -19,23 +18,37 @@ class ContactInboxBuilder
when 'Channel::Whatsapp'
wa_source_id
when 'Channel::Email'
@contact.email
email_source_id
when 'Channel::Sms'
@contact.phone_number
when 'Channel::Api'
phone_source_id
when 'Channel::Api', 'Channel::WebWidget'
SecureRandom.uuid
else
raise "Unsupported operation for this channel: #{@inbox.channel_type}"
end
end
def email_source_id
raise ActionController::ParameterMissing, 'contact email' unless @contact.email
@contact.email
end
def phone_source_id
raise ActionController::ParameterMissing, 'contact phone number' unless @contact.phone_number
@contact.phone_number
end
def wa_source_id
return unless @contact.phone_number
raise ActionController::ParameterMissing, 'contact phone number' unless @contact.phone_number
# whatsapp doesn't want the + in e164 format
@contact.phone_number.delete('+').to_s
end
def twilio_source_id
return unless @contact.phone_number
raise ActionController::ParameterMissing, 'contact phone number' unless @contact.phone_number
case @inbox.channel.medium
when 'sms'
@ -45,11 +58,11 @@ class ContactInboxBuilder
end
end
def create_contact_inbox(source_id)
::ContactInbox.find_or_create_by!(
def create_contact_inbox
::ContactInbox.create_with(hmac_verified: hmac_verified || false).find_or_create_by!(
contact_id: @contact.id,
inbox_id: @inbox.id,
source_id: source_id
source_id: @source_id
)
end
end

View file

@ -1,25 +1,47 @@
class ContactBuilder
pattr_initialize [:source_id!, :inbox!, :contact_attributes!, :hmac_verified]
# This Builder will create a contact and contact inbox with specified attributes.
# If an existing identified contact exisits, it will be returned.
# for contact inbox logic it uses the contact inbox builder
class ContactInboxWithContactBuilder
pattr_initialize [:inbox!, :contact_attributes!, :source_id, :hmac_verified]
def perform
contact_inbox = inbox.contact_inboxes.find_by(source_id: source_id)
return contact_inbox if contact_inbox
find_or_create_contact_and_contact_inbox
# in case of race conditions where contact is created by another thread
# we will try to find the contact and create a contact inbox
rescue ActiveRecord::RecordNotUnique
find_or_create_contact_and_contact_inbox
end
build_contact_inbox
def find_or_create_contact_and_contact_inbox
@contact_inbox = inbox.contact_inboxes.find_by(source_id: source_id) if source_id.present?
return @contact_inbox if @contact_inbox
ActiveRecord::Base.transaction(requires_new: true) do
build_contact_with_contact_inbox
update_contact_avatar(@contact) unless @contact.avatar.attached?
@contact_inbox
end
end
private
def build_contact_with_contact_inbox
@contact = find_contact || create_contact
@contact_inbox = create_contact_inbox
end
def account
@account ||= inbox.account
end
def create_contact_inbox(contact)
::ContactInbox.create_with(hmac_verified: hmac_verified || false).find_or_create_by!(
contact_id: contact.id,
inbox_id: inbox.id,
source_id: source_id
)
def create_contact_inbox
ContactInboxBuilder.new(
contact: @contact,
inbox: @inbox,
source_id: @source_id,
hmac_verified: hmac_verified
).perform
end
def update_contact_avatar(contact)
@ -61,16 +83,4 @@ class ContactBuilder
account.contacts.find_by(phone_number: phone_number)
end
def build_contact_inbox
ActiveRecord::Base.transaction do
contact = find_contact || create_contact
contact_inbox = create_contact_inbox(contact)
update_contact_avatar(contact)
contact_inbox
rescue StandardError => e
Rails.logger.error e
raise e
end
end
end

View file

@ -22,10 +22,9 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
return if @inbox.channel.reauthorization_required?
ActiveRecord::Base.transaction do
build_contact
build_contact_inbox
build_message
end
ensure_contact_avatar
rescue Koala::Facebook::AuthenticationError
@inbox.channel.authorization_error!
rescue StandardError => e
@ -35,15 +34,12 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
private
def contact
@contact ||= @inbox.contact_inboxes.find_by(source_id: @sender_id)&.contact
end
def build_contact
return if contact.present?
@contact = Contact.create!(contact_params.except(:remote_avatar_url))
@contact_inbox = ContactInbox.find_or_create_by!(contact: contact, inbox: @inbox, source_id: @sender_id)
def build_contact_inbox
@contact_inbox = ::ContactInboxWithContactBuilder.new(
source_id: @sender_id,
inbox: @inbox,
contact_attributes: contact_params
).perform
end
def build_message
@ -54,19 +50,11 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
end
end
def ensure_contact_avatar
return if contact_params[:remote_avatar_url].blank?
return if @contact.avatar.attached?
Avatar::AvatarFromUrlJob.perform_later(@contact, contact_params[:remote_avatar_url])
end
def conversation
@conversation ||= Conversation.find_by(conversation_params) || build_conversation
end
def build_conversation
@contact_inbox ||= contact.contact_inboxes.find_by!(source_id: @sender_id)
Conversation.create!(conversation_params.merge(
contact_inbox_id: @contact_inbox.id
))
@ -94,7 +82,7 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
{
account_id: @inbox.account_id,
inbox_id: @inbox.id,
contact_id: contact.id
contact_id: @contact_inbox.contact_id
}
end
@ -105,7 +93,7 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
message_type: @message_type,
content: response.content,
source_id: response.identifier,
sender: @outgoing_echo ? nil : contact
sender: @outgoing_echo ? nil : @contact_inbox.contact
}
end
@ -113,7 +101,7 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
{
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
account_id: @inbox.account_id,
remote_avatar_url: result['profile_pic'] || ''
avatar_url: result['profile_pic']
}
end

View file

@ -2,8 +2,11 @@ class Api::V1::Accounts::Contacts::ContactInboxesController < Api::V1::Accounts:
before_action :ensure_inbox, only: [:create]
def create
source_id = params[:source_id] || SecureRandom.uuid
@contact_inbox = ContactInbox.create!(contact: @contact, inbox: @inbox, source_id: source_id)
@contact_inbox = ContactInboxBuilder.new(
contact: @contact,
inbox: @inbox,
source_id: params[:source_id]
).perform
end
private

View file

@ -134,8 +134,11 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
return if params[:inbox_id].blank?
inbox = Current.account.inboxes.find(params[:inbox_id])
source_id = params[:source_id] || SecureRandom.uuid
ContactInbox.create!(contact: @contact, inbox: inbox, source_id: source_id)
ContactInboxBuilder.new(
contact: @contact,
inbox: inbox,
source_id: params[:source_id]
).perform
end
def permitted_params

View file

@ -3,7 +3,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
include DateRangeHelper
before_action :conversation, except: [:index, :meta, :search, :create, :filter]
before_action :contact_inbox, only: [:create]
before_action :inbox, :contact, :contact_inbox, only: [:create]
def index
result = conversation_finder.perform
@ -109,22 +109,35 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
authorize @conversation.inbox, :show?
end
def inbox
return if params[:inbox_id].blank?
@inbox = Current.account.inboxes.find(params[:inbox_id])
authorize @inbox, :show?
end
def contact
return if params[:contact_id].blank?
@contact = Current.account.contacts.find(params[:contact_id])
end
def contact_inbox
@contact_inbox = build_contact_inbox
# fallback for the old case where we do look up only using source id
# In future we need to change this and make sure we do look up on combination of inbox_id and source_id
# and deprecate the support of passing only source_id as the param
@contact_inbox ||= ::ContactInbox.find_by!(source_id: params[:source_id])
authorize @contact_inbox.inbox, :show?
end
def build_contact_inbox
return if params[:contact_id].blank? || params[:inbox_id].blank?
inbox = Current.account.inboxes.find(params[:inbox_id])
authorize inbox, :show?
return if @inbox.blank? || @contact.blank?
ContactInboxBuilder.new(
contact_id: params[:contact_id],
inbox_id: inbox.id,
contact: @contact,
inbox: @inbox,
source_id: params[:source_id]
).perform
end

View file

@ -22,7 +22,7 @@ class Api::V1::Accounts::CsatSurveyResponsesController < Api::V1::Accounts::Base
def download
response.headers['Content-Type'] = 'text/csv'
response.headers['Content-Disposition'] = 'attachment; filename=csat_report.csv'
render layout: false, template: 'api/v1/accounts/csat_survey_responses/download.csv.erb', format: 'csv'
render layout: false, template: 'api/v1/accounts/csat_survey_responses/download', formats: [:csv]
end
private

View file

@ -24,7 +24,7 @@ class Api::V1::AccountsController < Api::BaseController
).perform
if @user
send_auth_headers(@user)
render 'api/v1/accounts/create.json', locals: { resource: @user }
render 'api/v1/accounts/create', format: :json, locals: { resource: @user }
else
render_error_response(CustomExceptions::Account::SignupFailed.new({}))
end
@ -32,7 +32,7 @@ class Api::V1::AccountsController < Api::BaseController
def show
@latest_chatwoot_version = ::Redis::Alfred.get(::Redis::Alfred::LATEST_CHATWOOT_VERSION)
render 'api/v1/accounts/show.json'
render 'api/v1/accounts/show', format: :json
end
def update

View file

@ -22,6 +22,11 @@ class Api::V1::ProfilesController < Api::BaseController
@user.account_users.find_by!(account_id: availability_params[:account_id]).update!(availability: availability_params[:availability])
end
def set_active_account
@user.account_users.find_by(account_id: profile_params[:account_id]).update(active_at: Time.now.utc)
head :ok
end
private
def set_user
@ -39,6 +44,7 @@ class Api::V1::ProfilesController < Api::BaseController
:display_name,
:avatar,
:message_signature,
:account_id,
ui_settings: {}
)
end

View file

@ -14,22 +14,22 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
def agents
@report_data = generate_agents_report
generate_csv('agents_report', 'api/v2/accounts/reports/agents.csv.erb')
generate_csv('agents_report', 'api/v2/accounts/reports/agents')
end
def inboxes
@report_data = generate_inboxes_report
generate_csv('inboxes_report', 'api/v2/accounts/reports/inboxes.csv.erb')
generate_csv('inboxes_report', 'api/v2/accounts/reports/inboxes')
end
def labels
@report_data = generate_labels_report
generate_csv('labels_report', 'api/v2/accounts/reports/labels.csv.erb')
generate_csv('labels_report', 'api/v2/accounts/reports/labels')
end
def teams
@report_data = generate_teams_report
generate_csv('teams_report', 'api/v2/accounts/reports/teams.csv.erb')
generate_csv('teams_report', 'api/v2/accounts/reports/teams')
end
def conversations
@ -43,7 +43,7 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
def generate_csv(filename, template)
response.headers['Content-Type'] = 'text/csv'
response.headers['Content-Disposition'] = "attachment; filename=#{filename}.csv"
render layout: false, template: template, format: 'csv'
render layout: false, template: template, formats: [:csv]
end
def check_authorization

View file

@ -13,6 +13,8 @@ module RequestExceptionHandler
render_not_found_error('Resource could not be found')
rescue Pundit::NotAuthorizedError
render_unauthorized('You are not authorized to do this action')
rescue ActionController::ParameterMissing => e
render_could_not_create_error(e.message)
ensure
# to address the thread variable leak issues in Puma/Thin webserver
Current.reset

View file

@ -14,7 +14,7 @@ class DeviseOverrides::ConfirmationsController < Devise::ConfirmationsController
def render_confirmation_success
send_auth_headers(@confirmable)
render partial: 'devise/auth.json', locals: { resource: @confirmable }
render partial: 'devise/auth', formats: [:json], locals: { resource: @confirmable }
end
def render_confirmation_error

View file

@ -11,7 +11,7 @@ class DeviseOverrides::PasswordsController < Devise::PasswordsController
@recoverable = User.find_by(reset_password_token: reset_password_token)
if @recoverable && reset_password_and_confirmation(@recoverable)
send_auth_headers(@recoverable)
render partial: 'devise/auth.json', locals: { resource: @recoverable }
render partial: 'devise/auth', formats: [:json], locals: { resource: @recoverable }
else
render json: { message: 'Invalid token', redirect_url: '/' }, status: :unprocessable_entity
end

View file

@ -16,7 +16,7 @@ class DeviseOverrides::SessionsController < ::DeviseTokenAuth::SessionsControlle
end
def render_create_success
render partial: 'devise/auth.json', locals: { resource: @resource }
render partial: 'devise/auth', formats: [:json], locals: { resource: @resource }
end
private

View file

@ -2,7 +2,7 @@ class DeviseOverrides::TokenValidationsController < ::DeviseTokenAuth::TokenVali
def validate_token
# @resource will have been set by set_user_by_token concern
if @resource
render 'devise/token.json'
render 'devise/token', formats: [:json]
else
render_validate_token_error
end

View file

@ -51,6 +51,6 @@ class Platform::Api::V1::UsersController < PlatformController
end
def user_params
params.permit(:name, :email, :password, custom_attributes: {})
params.permit(:name, :display_name, :email, :password, custom_attributes: {})
end
end

View file

@ -4,7 +4,7 @@ class Public::Api::V1::Inboxes::ContactsController < Public::Api::V1::InboxesCon
def create
source_id = params[:source_id] || SecureRandom.uuid
@contact_inbox = ::ContactBuilder.new(
@contact_inbox = ::ContactInboxWithContactBuilder.new(
source_id: source_id,
inbox: @inbox_channel.inbox,
contact_attributes: permitted_params.except(:identifier, :identifier_hash)

View file

@ -87,6 +87,9 @@ export default {
},
async initializeAccount() {
await this.$store.dispatch('accounts/get');
this.$store.dispatch('setActiveAccount', {
accountId: this.currentAccountId,
});
const {
locale,
latest_chatwoot_version: latestChatwootVersion,

View file

@ -0,0 +1,9 @@
import ApiClient from './ApiClient';
class AgentBotsAPI extends ApiClient {
constructor() {
super('agent_bots', { accountScoped: true });
}
}
export default new AgentBotsAPI();

View file

@ -147,4 +147,13 @@ export default {
deleteAvatar() {
return axios.delete(endPoints('deleteAvatar').url);
},
setActiveAccount({ accountId }) {
const urlData = endPoints('setActiveAccount');
return axios.put(urlData.url, {
profile: {
account_id: accountId,
},
});
},
};

View file

@ -40,6 +40,10 @@ const endPoints = {
deleteAvatar: {
url: '/api/v1/profile/avatar',
},
setActiveAccount: {
url: '/api/v1/profile/set_active_account',
},
};
export default page => {

View file

@ -0,0 +1,16 @@
/* global axios */
import ApiClient from './ApiClient';
class MacrosAPI extends ApiClient {
constructor() {
super('macros', { accountScoped: true });
}
executeMacro({ macroId, conversationIds }) {
return axios.post(`${this.url}/${macroId}/execute`, {
conversation_ids: conversationIds,
});
}
}
export default new MacrosAPI();

View file

@ -0,0 +1,13 @@
import AgentBotsAPI from '../agentBots';
import ApiClient from '../ApiClient';
describe('#AgentBotsAPI', () => {
it('creates correct instance', () => {
expect(AgentBotsAPI).toBeInstanceOf(ApiClient);
expect(AgentBotsAPI).toHaveProperty('get');
expect(AgentBotsAPI).toHaveProperty('show');
expect(AgentBotsAPI).toHaveProperty('create');
expect(AgentBotsAPI).toHaveProperty('update');
expect(AgentBotsAPI).toHaveProperty('delete');
});
});

View file

@ -0,0 +1,14 @@
import macros from '../macros';
import ApiClient from '../ApiClient';
describe('#macrosAPI', () => {
it('creates correct instance', () => {
expect(macros).toBeInstanceOf(ApiClient);
expect(macros).toHaveProperty('get');
expect(macros).toHaveProperty('create');
expect(macros).toHaveProperty('update');
expect(macros).toHaveProperty('delete');
expect(macros).toHaveProperty('show');
expect(macros.url).toBe('/api/v1/macros');
});
});

View file

@ -113,9 +113,22 @@ $default-button-height: 4.0rem;
}
&.clear {
color: var(--w-700);
&.secondary {
color: var(--s-700)
}
&.success {
color: var(--g-700)
}
&.alert {
color: var(--r-700)
}
&.warning {
color: var(--y-600);
color: var(--y-700)
}
&:hover {
@ -146,6 +159,8 @@ $default-button-height: 4.0rem;
&.small {
height: var(--space-large);
padding-bottom: var(--space-smaller);
padding-top: var(--space-smaller);
}
&.large {

View file

@ -14,15 +14,9 @@
}
.modal--close {
border-radius: 50%;
color: $color-heading;
cursor: pointer;
font-size: $font-size-big;
line-height: $space-normal;
padding: $space-normal;
position: absolute;
right: $space-micro;
top: $space-micro;
right: $space-small;
top: $space-small;
&:hover {
background: $color-background;

View file

@ -7,9 +7,13 @@
@click="onBackDropClick"
>
<div :class="modalContainerClassName" @click.stop>
<button class="modal--close" @click="close">
<fluent-icon icon="dismiss" />
</button>
<woot-button
color-scheme="secondary"
icon="dismiss"
variant="clear"
class="modal--close"
@click="close"
/>
<slot />
</div>
</div>

View file

@ -73,14 +73,14 @@ export default {
computed: {
...mapGetters({
currentUser: 'getCurrentUser',
globalConfig: 'globalConfig/get',
isACustomBrandedInstance: 'globalConfig/isACustomBrandedInstance',
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
inboxes: 'inboxes/getInboxes',
accountId: 'getCurrentAccountId',
currentRole: 'getCurrentRole',
currentUser: 'getCurrentUser',
globalConfig: 'globalConfig/get',
inboxes: 'inboxes/getInboxes',
isACustomBrandedInstance: 'globalConfig/isACustomBrandedInstance',
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
labels: 'labels/getLabelsOnSidebar',
teams: 'teams/getMyTeams',
}),

View file

@ -39,7 +39,7 @@ const primaryMenuItems = accountId => [
label: 'HELP_CENTER.TITLE',
featureFlag: 'help_center',
toState: frontendURL(`accounts/${accountId}/portals`),
toStateName: 'list_all_portals',
toStateName: 'default_portal_articles',
roles: ['administrator'],
},
{

View file

@ -1,45 +1,58 @@
import { FEATURE_FLAGS } from '../../../../featureFlags';
import { frontendURL } from '../../../../helper/URLHelper';
const settings = accountId => ({
parentNav: 'settings',
routes: [
'agent_bots',
'agent_list',
'canned_list',
'labels_list',
'settings_inbox',
'attributes_list',
'settings_inbox_new',
'settings_inbox_list',
'settings_inbox_show',
'settings_inboxes_page_channel',
'settings_inboxes_add_agents',
'settings_inbox_finish',
'settings_integrations',
'settings_integrations_webhook',
'settings_integrations_integration',
'settings_applications',
'settings_integrations_dashboard_apps',
'settings_applications_webhook',
'settings_applications_integration',
'general_settings',
'automation_list',
'billing_settings_index',
'canned_list',
'general_settings_index',
'general_settings',
'labels_list',
'macros_edit',
'macros_new',
'macros_wrapper',
'settings_applications_integration',
'settings_applications_webhook',
'settings_applications',
'settings_inbox_finish',
'settings_inbox_list',
'settings_inbox_new',
'settings_inbox_show',
'settings_inbox',
'settings_inboxes_add_agents',
'settings_inboxes_page_channel',
'settings_integrations_dashboard_apps',
'settings_integrations_integration',
'settings_integrations_webhook',
'settings_integrations',
'settings_teams_add_agents',
'settings_teams_edit_finish',
'settings_teams_edit_members',
'settings_teams_edit',
'settings_teams_finish',
'settings_teams_list',
'settings_teams_new',
'settings_teams_add_agents',
'settings_teams_finish',
'settings_teams_edit',
'settings_teams_edit_members',
'settings_teams_edit_finish',
'billing_settings_index',
'automation_list',
],
menuItems: [
{
icon: 'briefcase',
label: 'ACCOUNT_SETTINGS',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/general`),
toStateName: 'general_settings_index',
},
{
icon: 'people',
label: 'AGENTS',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/agents/list`),
toStateName: 'agent_list',
featureFlag: FEATURE_FLAGS.AGENT_MANAGEMENT,
},
{
icon: 'people-team',
@ -47,6 +60,7 @@ const settings = accountId => ({
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/teams/list`),
toStateName: 'settings_teams_list',
featureFlag: FEATURE_FLAGS.TEAM_MANAGEMENT,
},
{
icon: 'mail-inbox-all',
@ -54,6 +68,7 @@ const settings = accountId => ({
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/inboxes/list`),
toStateName: 'settings_inbox_list',
featureFlag: FEATURE_FLAGS.INBOX_MANAGEMENT,
},
{
icon: 'tag',
@ -61,6 +76,7 @@ const settings = accountId => ({
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/labels/list`),
toStateName: 'labels_list',
featureFlag: FEATURE_FLAGS.LABELS,
},
{
icon: 'code',
@ -70,13 +86,34 @@ const settings = accountId => ({
`accounts/${accountId}/settings/custom-attributes/list`
),
toStateName: 'attributes_list',
featureFlag: FEATURE_FLAGS.CUSTOM_ATTRIBUTES,
},
{
icon: 'automation',
label: 'AUTOMATION',
beta: true,
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/automation/list`),
toStateName: 'automation_list',
featureFlag: FEATURE_FLAGS.AUTOMATIONS,
},
{
icon: 'bot',
label: 'AGENT_BOTS',
beta: true,
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/agent-bots`),
toStateName: 'agent_bots',
featureFlag: FEATURE_FLAGS.AGENT_BOTS,
},
{
icon: 'flash-settings',
label: 'MACROS',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/macros`),
toStateName: 'macros_wrapper',
beta: true,
featureFlag: FEATURE_FLAGS.MACROS,
},
{
icon: 'chat-multiple',
@ -86,6 +123,7 @@ const settings = accountId => ({
`accounts/${accountId}/settings/canned-response/list`
),
toStateName: 'canned_list',
featureFlag: FEATURE_FLAGS.CANNED_RESPONSES,
},
{
icon: 'flash-on',
@ -93,6 +131,7 @@ const settings = accountId => ({
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/integrations`),
toStateName: 'settings_integrations',
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
},
{
icon: 'star-emphasis',
@ -100,6 +139,7 @@ const settings = accountId => ({
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/applications`),
toStateName: 'settings_applications',
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
},
{
icon: 'credit-card-person',
@ -109,13 +149,6 @@ const settings = accountId => ({
toStateName: 'billing_settings_index',
showOnlyOnCloud: true,
},
{
icon: 'settings',
label: 'ACCOUNT_SETTINGS',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/general`),
toStateName: 'general_settings_index',
},
],
});

View file

@ -8,25 +8,33 @@
:header-title="$t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS')"
:header-content="$t('SIDEBAR_ITEMS.SELECTOR_SUBTITLE')"
/>
<div
v-for="account in currentUser.accounts"
:key="account.id"
class="account-selector"
>
<a :href="`/app/accounts/${account.id}/dashboard`">
<fluent-icon
v-if="account.id === accountId"
class="selected--account"
icon="checkmark-circle"
type="solid"
size="24"
/>
<label :for="account.name" class="account--details">
<div class="account--name">{{ account.name }}</div>
<div class="account--role">{{ account.role }}</div>
</label>
</a>
<div class="account-selector--wrap">
<div
v-for="account in currentUser.accounts"
:key="account.id"
class="account-selector"
>
<button
class="button expanded clear link"
@click="onChangeAccount(account.id)"
>
<span class="button__content">
<label :for="account.name" class="account-details--wrap">
<div class="account--name">{{ account.name }}</div>
<div class="account--role">{{ account.role }}</div>
</label>
</span>
<fluent-icon
v-show="account.id === accountId"
class="selected--account"
icon="checkmark-circle"
type="solid"
size="24"
/>
</button>
</div>
</div>
<div
v-if="globalConfig.createNewAccountFromDashboard"
class="modal-footer delete-item"
@ -58,5 +66,40 @@ export default {
globalConfig: 'globalConfig/get',
}),
},
methods: {
onChangeAccount(accountId) {
const accountUrl = `/app/accounts/${accountId}/dashboard`;
window.location.href = accountUrl;
},
},
};
</script>
<style lang="scss" scoped>
.account-selector--wrap {
margin-top: var(--space-normal);
}
.account-selector {
padding-top: 0;
padding-bottom: 0;
.button {
display: flex;
justify-content: space-between;
padding: var(--space-one) var(--space-normal);
.account-details--wrap {
text-align: left;
.account--name {
cursor: pointer;
font-size: var(--font-size-medium);
font-weight: var(--font-weight-medium);
line-height: 1;
}
.account--role {
cursor: pointer;
font-size: var(--font-size-mini);
text-transform: capitalize;
}
}
}
}
</style>

View file

@ -20,6 +20,8 @@
import { frontendURL } from '../../../helper/URLHelper';
import SecondaryNavItem from './SecondaryNavItem.vue';
import AccountContext from './AccountContext.vue';
import { mapGetters } from 'vuex';
import { FEATURE_FLAGS } from '../../../featureFlags';
export default {
components: {
@ -61,6 +63,9 @@ export default {
},
},
computed: {
...mapGetters({
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
}),
hasSecondaryMenu() {
return this.menuConfig.menuItems && this.menuConfig.menuItems.length;
},
@ -89,7 +94,7 @@ export default {
icon: 'folder',
label: 'INBOXES',
hasSubMenu: true,
newLink: true,
newLink: this.showNewLink(FEATURE_FLAGS.INBOX_MANAGEMENT),
newLinkTag: 'NEW_INBOX',
key: 'inbox',
toState: frontendURL(`accounts/${this.accountId}/settings/inboxes/new`),
@ -117,7 +122,7 @@ export default {
icon: 'number-symbol',
label: 'LABELS',
hasSubMenu: true,
newLink: true,
newLink: this.showNewLink(FEATURE_FLAGS.TEAM_MANAGEMENT),
newLinkTag: 'NEW_LABEL',
key: 'label',
toState: frontendURL(`accounts/${this.accountId}/settings/labels`),
@ -141,7 +146,7 @@ export default {
label: 'TAGGED_WITH',
hasSubMenu: true,
key: 'label',
newLink: true,
newLink: this.showNewLink(FEATURE_FLAGS.TEAM_MANAGEMENT),
newLinkTag: 'NEW_LABEL',
toState: frontendURL(`accounts/${this.accountId}/settings/labels`),
toStateName: 'labels_list',
@ -163,7 +168,7 @@ export default {
icon: 'people-team',
label: 'TEAMS',
hasSubMenu: true,
newLink: true,
newLink: this.showNewLink(FEATURE_FLAGS.TEAM_MANAGEMENT),
newLinkTag: 'NEW_TEAM',
key: 'team',
toState: frontendURL(`accounts/${this.accountId}/settings/teams/new`),
@ -238,6 +243,9 @@ export default {
toggleAccountModal() {
this.$emit('toggle-accounts');
},
showNewLink(featureFlag) {
return this.isFeatureEnabledonAccount(this.accountId, featureFlag);
},
},
};
</script>

View file

@ -1,5 +1,5 @@
<template>
<li class="sidebar-item">
<li v-show="isMenuItemVisible" class="sidebar-item">
<div v-if="hasSubMenu" class="secondary-menu--wrap">
<span class="secondary-menu--header fs-small">
{{ $t(`SIDEBAR.${menuItem.label}`) }}
@ -36,7 +36,7 @@
{{ `${menuItem.count}` }}
</span>
<span
v-if="menuItem.label === 'AUTOMATION'"
v-if="menuItem.beta"
data-view-component="true"
label="Beta"
class="beta"
@ -114,10 +114,23 @@ export default {
},
},
computed: {
...mapGetters({ activeInbox: 'getSelectedInbox' }),
...mapGetters({
activeInbox: 'getSelectedInbox',
accountId: 'getCurrentAccountId',
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
}),
hasSubMenu() {
return !!this.menuItem.children;
},
isMenuItemVisible() {
if (!this.menuItem.featureFlag) {
return true;
}
return this.isFeatureEnabledonAccount(
this.accountId,
this.menuItem.featureFlag
);
},
isInboxConversation() {
return (
this.$store.state.route.name === 'inbox_conversation' &&
@ -204,7 +217,7 @@ export default {
}
},
showItem(item) {
return this.isAdmin && item.newLink !== undefined;
return this.isAdmin && !!item.newLink;
},
onClickOpen() {
this.$emit('open');
@ -308,7 +321,7 @@ export default {
.beta {
padding-right: var(--space-smaller) !important;
padding-left: var(--space-smaller) !important;
margin-left: var(--space-half) !important;
margin-left: var(--space-smaller) !important;
display: inline-block;
font-size: var(--font-size-micro);
font-weight: var(--font-weight-medium);

View file

@ -0,0 +1,113 @@
<template>
<div class="preview-card--wrap" :class="{ activecard: active }">
<div class="header--wrap" :class="{ active: active }">
<div class="heading-wrap text-block-title">{{ heading }}</div>
<fluent-icon
v-if="active"
icon="checkmark-circle"
type="solid"
size="24"
class="checkmark"
/>
</div>
<div class="content-wrap">
{{ content }}
</div>
<div class="image-wrap">
<img :src="src" class="image" :class="{ activeimage: active }" />
</div>
</div>
</template>
<script>
export default {
props: {
heading: {
type: String,
default: '',
},
content: {
type: String,
default: '',
},
active: {
type: Boolean,
default: false,
},
buttonText: {
type: String,
default: 'Active',
},
src: {
type: String,
default: '',
},
},
};
</script>
<style lang="scss" scoped>
.preview-card--wrap {
border-radius: var(--border-radius-normal);
border: 1px solid var(--color-border);
display: flex;
flex-direction: column;
max-height: 34rem;
max-width: 38rem;
min-width: 24rem;
.header--wrap {
background: var(--s-50);
border-bottom: 1px solid var(--color-border);
border-top-left-radius: var(--border-radius-normal);
border-top-right-radius: var(--border-radius-normal);
display: flex;
height: 4rem;
justify-content: space-between;
padding: var(--space-small);
width: 100%;
}
.active {
background: var(--w-50);
border-bottom: 1px solid var(--w-75);
}
.heading-wrap {
align-items: center;
display: flex;
font-weight: var(--font-weight-medium);
padding: var(--space-smaller);
}
.checkmark {
color: var(--w-500);
}
.content-wrap {
color: var(--s-700);
font-size: var(--font-size-mini);
line-height: 1.4;
padding: var(--space-slab) var(--space-slab) 0 var(--space-slab);
text-align: start;
}
.image-wrap {
padding: var(--space-slab);
}
.image {
border: 1px solid var(--color-border);
border-radius: var(--border-radius-normal);
}
.activeimage {
border: 1px solid var(--w-75);
}
}
.activecard {
background: var(--w-25);
border: 1px solid var(--w-300);
}
</style>

View file

@ -78,7 +78,7 @@ export default {
if (initials.length > 2 && initials.search(/[A-Z]/) !== -1) {
initials = initials.replace(/[a-z]+/g, '');
}
initials = initials.substr(0, 2).toUpperCase();
initials = initials.substring(0, 2).toUpperCase();
return initials;
},
},

View file

@ -6,7 +6,7 @@
@click="insertMentionNode"
/>
<canned-response
v-if="showCannedMenu"
v-if="showCannedMenu && !isPrivate"
:search-key="cannedSearchTerm"
@click="insertCannedResponse"
/>
@ -28,7 +28,7 @@ import {
suggestionsPlugin,
triggerCharacters,
} from '@chatwoot/prosemirror-schema/src/mentions/plugin';
import { EditorState } from 'prosemirror-state';
import { EditorState, Selection } from 'prosemirror-state';
import { defaultMarkdownParser } from 'prosemirror-markdown';
import { wootWriterSetup } from '@chatwoot/prosemirror-schema';
@ -61,23 +61,28 @@ export default {
mixins: [eventListenerMixins],
props: {
value: { type: String, default: '' },
editorId: { type: String, default: '' },
placeholder: { type: String, default: '' },
isPrivate: { type: Boolean, default: false },
isFormatMode: { type: Boolean, default: false },
enableSuggestions: { type: Boolean, default: true },
},
data() {
return {
lastValue: null,
showUserMentions: false,
showCannedMenu: false,
mentionSearchKey: '',
cannedSearchTerm: '',
editorView: null,
range: null,
state: undefined,
};
},
computed: {
contentFromEditor() {
return addMentionsToMarkdownSerializer(
defaultMarkdownSerializer
).serialize(this.editorView.state.doc);
},
plugins() {
if (!this.enableSuggestions) {
return [];
@ -102,7 +107,6 @@ export default {
onExit: () => {
this.mentionSearchKey = '';
this.showUserMentions = false;
this.editorView = null;
return false;
},
onKeyDown: ({ event }) => {
@ -131,7 +135,6 @@ export default {
onExit: () => {
this.cannedSearchTerm = '';
this.showCannedMenu = false;
this.editorView = null;
return false;
},
onKeyDown: ({ event }) => {
@ -149,54 +152,57 @@ export default {
this.$emit('toggle-canned-menu', !this.isPrivate && updatedValue);
},
value(newValue = '') {
if (newValue !== this.lastValue) {
const { tr } = this.state;
if (this.isFormatMode) {
this.state = createState(
newValue,
this.placeholder,
this.plugins,
this.isFormatMode
);
} else {
tr.insertText(newValue, 0, tr.doc.content.size);
this.state = this.view.state.apply(tr);
}
this.view.updateState(this.state);
if (newValue !== this.contentFromEditor) {
this.reloadState();
}
},
editorId() {
this.reloadState();
},
isPrivate() {
this.reloadState();
},
},
created() {
this.state = createState(this.value, this.placeholder, this.plugins);
},
mounted() {
this.view = new EditorView(this.$refs.editor, {
state: this.state,
dispatchTransaction: tx => {
this.state = this.state.apply(tx);
this.emitOnChange();
},
handleDOMEvents: {
keyup: () => {
this.onKeyup();
},
focus: () => {
this.onFocus();
},
blur: () => {
this.onBlur();
},
paste: (view, event) => {
const data = event.clipboardData.files;
if (data.length > 0) {
event.preventDefault();
}
},
},
});
this.createEditorView();
this.editorView.updateState(this.state);
this.focusEditorInputField();
},
methods: {
reloadState() {
this.state = createState(this.value, this.placeholder, this.plugins);
this.editorView.updateState(this.state);
this.focusEditorInputField();
},
createEditorView() {
this.editorView = new EditorView(this.$refs.editor, {
state: this.state,
dispatchTransaction: tx => {
this.state = this.state.apply(tx);
this.emitOnChange();
},
handleDOMEvents: {
keyup: () => {
this.onKeyup();
},
focus: () => {
this.onFocus();
},
blur: () => {
this.onBlur();
},
paste: (view, event) => {
const data = event.clipboardData.files;
if (data.length > 0) {
event.preventDefault();
}
},
},
});
},
handleKeyEvents(e) {
if (hasPressedAltAndPKey(e)) {
this.focusEditorInputField();
@ -206,47 +212,60 @@ export default {
}
},
focusEditorInputField() {
this.$refs.editor.querySelector('div.ProseMirror-woot-style').focus();
const { tr } = this.editorView.state;
const selection = Selection.atEnd(tr.doc);
this.editorView.dispatch(tr.setSelection(selection));
this.editorView.focus();
},
insertMentionNode(mentionItem) {
if (!this.view) {
if (!this.editorView) {
return null;
}
const node = this.view.state.schema.nodes.mention.create({
userId: mentionItem.key,
userFullName: mentionItem.label,
const node = this.editorView.state.schema.nodes.mention.create({
userId: mentionItem.id,
userFullName: mentionItem.name,
});
const tr = this.view.state.tr.replaceWith(
const tr = this.editorView.state.tr.replaceWith(
this.range.from,
this.range.to,
node
);
this.state = this.view.state.apply(tr);
this.state = this.editorView.state.apply(tr);
return this.emitOnChange();
},
insertCannedResponse(cannedItem) {
if (!this.view) {
if (!this.editorView) {
return null;
}
const tr = this.view.state.tr.insertText(
const tr = this.editorView.state.tr.insertText(
cannedItem,
this.range.from,
this.range.to
);
this.state = this.view.state.apply(tr);
return this.emitOnChange();
this.state = this.editorView.state.apply(tr);
this.emitOnChange();
// Hacky fix for #5501
this.state = createState(
this.contentFromEditor,
this.placeholder,
this.plugins
);
this.editorView.updateState(this.state);
this.focusEditorInputField();
return false;
},
emitOnChange() {
this.view.updateState(this.state);
this.lastValue = addMentionsToMarkdownSerializer(
defaultMarkdownSerializer
).serialize(this.state.doc);
this.$emit('input', this.lastValue);
this.editorView.updateState(this.state);
this.$emit('input', this.contentFromEditor);
},
hideMentions() {
this.showUserMentions = false;
},

View file

@ -11,7 +11,6 @@
size="small"
@click="toggleEmojiPicker"
/>
<!-- ensure the same validations for attachment types are implemented in backend models as well -->
<file-upload
ref="upload"
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
@ -47,6 +46,16 @@
:title="$t('CONVERSATION.REPLYBOX.TIP_AUDIORECORDER_ICON')"
@click="toggleAudioRecorder"
/>
<woot-button
v-if="showEditorToggle"
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
icon="quote"
emoji="🖊️"
color-scheme="secondary"
variant="smooth"
size="small"
@click="$emit('toggle-editor')"
/>
<woot-button
v-if="showAudioPlayStopButton"
:icon="audioRecorderPlayStopIcon"
@ -91,17 +100,6 @@
</transition>
</div>
<div class="right-wrap">
<div v-if="isFormatMode" class="enter-to-send--checkbox">
<input
:checked="enterToSendEnabled"
type="checkbox"
value="enterToSend"
@input="toggleEnterToSend"
/>
<label for="enterToSend">
{{ $t('CONVERSATION.REPLYBOX.ENTER_TO_SEND') }}
</label>
</div>
<woot-button
size="small"
:class-names="buttonClass"
@ -121,13 +119,15 @@ import { hasPressedAltAndAKey } from 'shared/helpers/KeyboardHelpers';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import inboxMixin from 'shared/mixins/inboxMixin';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
import {
ALLOWED_FILE_TYPES,
ALLOWED_FILE_TYPES_FOR_TWILIO_WHATSAPP,
} from 'shared/constants/messages';
import { REPLY_EDITOR_MODES } from './constants';
import { mapGetters } from 'vuex';
export default {
name: 'ReplyBottomPanel',
components: { FileUpload },
@ -193,7 +193,7 @@ export default {
type: Boolean,
default: false,
},
isFormatMode: {
showEditorToggle: {
type: Boolean,
default: false,
},
@ -201,10 +201,6 @@ export default {
type: Boolean,
default: false,
},
enterToSendEnabled: {
type: Boolean,
default: true,
},
enableMultipleFileUpload: {
type: Boolean,
default: true,
@ -215,6 +211,10 @@ export default {
},
},
computed: {
...mapGetters({
accountId: 'getCurrentAccountId',
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
}),
isNote() {
return this.mode === REPLY_EDITOR_MODES.NOTE;
},
@ -232,7 +232,12 @@ export default {
return this.showFileUpload || this.isNote;
},
showAudioRecorderButton() {
return this.showAudioRecorder;
return (
this.isFeatureEnabledonAccount(
this.accountId,
FEATURE_FLAGS.VOICE_RECORDER
) && this.showAudioRecorder
);
},
showAudioPlayStopButton() {
return this.showAudioRecorder && this.isRecordingAudio;
@ -278,9 +283,6 @@ export default {
this.$refs.upload.$children[1].$el.click();
}
},
toggleEnterToSend() {
this.$emit('toggleEnterToSend', !this.enterToSendEnabled);
},
toggleMessageSignature() {
this.updateUISettings({
send_with_signature: !this.sendWithSignature,
@ -312,20 +314,6 @@ export default {
.right-wrap {
display: flex;
.enter-to-send--checkbox {
align-items: center;
display: flex;
input {
margin: 0;
}
label {
color: var(--s-500);
font-size: var(--font-size-mini);
}
}
}
::v-deep .file-uploads {

View file

@ -100,10 +100,11 @@
v-if="isBubble && !isMessageDeleted"
:is-open="showContextMenu"
:show-copy="hasText"
:show-canned-response-option="isOutgoing"
:menu-position="contextMenuPosition"
:message-content="data.content"
@toggle="handleContextMenuClick"
@delete="handleDelete"
@copy="handleCopy"
/>
</div>
</li>
@ -126,7 +127,6 @@ import alertMixin from 'shared/mixins/alertMixin';
import contentTypeMixin from 'shared/mixins/contentTypeMixin';
import { MESSAGE_TYPE, MESSAGE_STATUS } from 'shared/constants/messages';
import { generateBotMessageContent } from './helpers/botMessageContentHelper';
import { copyTextToClipboard } from 'shared/helpers/clipboard';
export default {
components: {
@ -408,11 +408,6 @@ export default {
this.showAlert(this.$t('CONVERSATION.FAIL_DELETE_MESSSAGE'));
}
},
async handleCopy() {
await copyTextToClipboard(this.data.content);
this.showAlert(this.$t('CONTACT_PANEL.COPY_SUCCESSFUL'));
this.showContextMenu = false;
},
async retrySendMessage() {
await this.$store.dispatch('sendMessageWithData', this.data);
},
@ -425,6 +420,8 @@ export default {
<style lang="scss">
.wrap {
> .bubble {
min-width: 128px;
&.is-image,
&.is-video {
padding: 0;

View file

@ -434,7 +434,7 @@ export default {
&::before {
transform: rotate(0deg);
left: var(--space-half);
left: var(--space-smaller);
bottom: var(--space-minus-slab);
}
}

View file

@ -56,6 +56,7 @@
<woot-message-editor
v-else
v-model="message"
:editor-id="editorStateId"
class="input"
:is-private="isOnPrivateNote"
:placeholder="messagePlaceHolder"
@ -108,12 +109,11 @@
:recording-audio-state="recordingAudioState"
:is-recording-audio="isRecordingAudio"
:is-on-private-note="isOnPrivateNote"
:is-format-mode="showRichContentEditor"
:enter-to-send-enabled="enterToSendEnabled"
:show-editor-toggle="isAPIInbox && !isOnPrivateNote"
:enable-multiple-file-upload="enableMultipleFileUpload"
:has-whatsapp-templates="hasWhatsappTemplates"
@toggleEnterToSend="toggleEnterToSend"
@selectWhatsappTemplate="openWhatsappTemplateModal"
@toggle-editor="toggleRichContentEditor"
/>
<whatsapp-templates
:inbox-id="inbox.id"
@ -148,19 +148,18 @@ import {
MAXIMUM_FILE_UPLOAD_SIZE_TWILIO_SMS_CHANNEL,
} from 'shared/constants/messages';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import WhatsappTemplates from './WhatsappTemplates/Modal.vue';
import {
isEscape,
isEnter,
hasPressedShift,
hasPressedCommandPlusKKey,
} from 'shared/helpers/KeyboardHelpers';
import { buildHotKeys } from 'shared/helpers/KeyboardHelpers';
import { MESSAGE_MAX_LENGTH } from 'shared/helpers/MessageTypeHelper';
import inboxMixin from 'shared/mixins/inboxMixin';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import { DirectUpload } from 'activestorage';
import { frontendURL } from '../../../helper/URLHelper';
import { LocalStorage, LOCAL_STORAGE_KEYS } from '../../../helper/localStorage';
import { trimContent, debounce } from '@chatwoot/utils';
import wootConstants from 'dashboard/constants';
import { isEditorHotKeyEnabled } from 'dashboard/mixins/uiSettings';
export default {
components: {
@ -214,6 +213,7 @@ export default {
hasSlashCommand: false,
bccEmails: '',
ccEmails: '',
doAutoSaveDraft: () => {},
showWhatsAppTemplatesModal: false,
};
},
@ -231,6 +231,13 @@ export default {
return true;
}
if (this.isAPIInbox) {
const {
display_rich_content_editor: displayRichContentEditor = false,
} = this.uiSettings;
return displayRichContentEditor;
}
return false;
},
assignedAgent: {
@ -266,9 +273,6 @@ export default {
return !!this.$store.getters['inboxes/getWhatsAppTemplates'](this.inboxId)
.length;
},
enterToSendEnabled() {
return !!this.uiSettings.enter_to_send_enabled;
},
isPrivate() {
if (this.currentChat.can_reply || this.isAWhatsAppChannel) {
return this.isOnPrivateNote;
@ -342,13 +346,16 @@ export default {
);
},
replyButtonLabel() {
let sendMessageText = this.$t('CONVERSATION.REPLYBOX.SEND');
if (this.isPrivate) {
return this.$t('CONVERSATION.REPLYBOX.CREATE');
sendMessageText = this.$t('CONVERSATION.REPLYBOX.CREATE');
} else if (this.conversationType === 'tweet') {
sendMessageText = this.$t('CONVERSATION.REPLYBOX.TWEET');
}
if (this.conversationType === 'tweet') {
return this.$t('CONVERSATION.REPLYBOX.TWEET');
}
return this.$t('CONVERSATION.REPLYBOX.SEND');
const keyLabel = isEditorHotKeyEnabled(this.uiSettings, 'cmd_enter')
? '(⌘ + ↵)'
: '(↵)';
return `${sendMessageText} ${keyLabel}`;
},
replyBoxClass() {
return {
@ -366,7 +373,7 @@ export default {
);
},
isRichEditorEnabled() {
return this.isAWebWidgetInbox || this.isAnEmailChannel || this.isAPIInbox;
return this.isAWebWidgetInbox || this.isAnEmailChannel;
},
showAudioRecorder() {
return !this.isOnPrivateNote && this.showFileUpload;
@ -429,10 +436,31 @@ export default {
profilePath() {
return frontendURL(`accounts/${this.accountId}/profile/settings`);
},
editorMessageKey() {
const { editor_message_key: isEnabled } = this.uiSettings;
return isEnabled;
},
commandPlusEnterToSendEnabled() {
return this.editorMessageKey === 'cmd_enter';
},
enterToSendEnabled() {
return this.editorMessageKey === 'enter';
},
conversationId() {
return this.currentChat.id;
},
conversationIdByRoute() {
const { conversation_id: conversationId } = this.$route.params;
return conversationId;
},
editorStateId() {
return `draft-${this.conversationIdByRoute}-${this.replyType}`;
},
},
watch: {
currentChat(conversation) {
const { can_reply: canReply } = conversation;
if (this.isOnPrivateNote) {
return;
}
@ -445,34 +473,135 @@ export default {
this.setCCEmailFromLastChat();
},
conversationIdByRoute(conversationId, oldConversationId) {
if (conversationId !== oldConversationId) {
this.setToDraft(oldConversationId, this.replyType);
this.getFromDraft();
}
},
message(updatedMessage) {
this.hasSlashCommand =
updatedMessage[0] === '/' && !this.showRichContentEditor;
const hasNextWord = updatedMessage.includes(' ');
const isShortCodeActive = this.hasSlashCommand && !hasNextWord;
if (isShortCodeActive) {
this.mentionSearchKey = updatedMessage.substr(1, updatedMessage.length);
this.mentionSearchKey = updatedMessage.substring(1);
this.showMentions = true;
} else {
this.mentionSearchKey = '';
this.showMentions = false;
}
this.doAutoSaveDraft();
},
replyType(updatedReplyType, oldReplyType) {
this.setToDraft(this.conversationIdByRoute, oldReplyType);
this.getFromDraft();
},
},
mounted() {
this.getFromDraft();
// Donot use the keyboard listener mixin here as the events here are supposed to be
// working even if input/textarea is focussed.
document.addEventListener('keydown', this.handleKeyEvents);
document.addEventListener('paste', this.onPaste);
document.addEventListener('keydown', this.handleKeyEvents);
this.setCCEmailFromLastChat();
this.doAutoSaveDraft = debounce(
() => {
this.saveDraft(this.conversationIdByRoute, this.replyType);
},
500,
true
);
},
destroyed() {
document.removeEventListener('keydown', this.handleKeyEvents);
document.removeEventListener('paste', this.onPaste);
document.removeEventListener('keydown', this.handleKeyEvents);
},
methods: {
toggleRichContentEditor() {
this.updateUISettings({
display_rich_content_editor: !this.showRichContentEditor,
});
},
getSavedDraftMessages() {
return LocalStorage.get(LOCAL_STORAGE_KEYS.DRAFT_MESSAGES) || {};
},
saveDraft(conversationId, replyType) {
if (this.message || this.message === '') {
const savedDraftMessages = this.getSavedDraftMessages();
const key = `draft-${conversationId}-${replyType}`;
const draftToSave = trimContent(this.message || '');
const {
[key]: currentDraft,
...restOfDraftMessages
} = savedDraftMessages;
const updatedDraftMessages = draftToSave
? {
...restOfDraftMessages,
[key]: draftToSave,
}
: restOfDraftMessages;
LocalStorage.set(
LOCAL_STORAGE_KEYS.DRAFT_MESSAGES,
updatedDraftMessages
);
}
},
setToDraft(conversationId, replyType) {
this.saveDraft(conversationId, replyType);
this.message = '';
},
getFromDraft() {
if (this.conversationIdByRoute) {
try {
const key = `draft-${this.conversationIdByRoute}-${this.replyType}`;
const savedDraftMessages = this.getSavedDraftMessages();
this.message = `${savedDraftMessages[key] || ''}`;
} catch (error) {
this.message = '';
}
}
},
removeFromDraft() {
if (this.conversationIdByRoute) {
const key = `draft-${this.conversationIdByRoute}-${this.replyType}`;
const draftMessages = this.getSavedDraftMessages();
const { [key]: toBeRemoved, ...updatedDraftMessages } = draftMessages;
LocalStorage.set(
LOCAL_STORAGE_KEYS.DRAFT_MESSAGES,
updatedDraftMessages
);
}
},
handleKeyEvents(e) {
const keyCode = buildHotKeys(e);
if (keyCode === 'escape') {
this.hideEmojiPicker();
this.hideMentions();
} else if (keyCode === 'meta+k') {
const ninja = document.querySelector('ninja-keys');
ninja.open();
e.preventDefault();
} else if (keyCode === 'enter' && this.isAValidEvent('enter')) {
this.onSendReply();
} else if (
['meta+enter', 'ctrl+enter'].includes(keyCode) &&
this.isAValidEvent('cmd_enter')
) {
this.onSendReply();
}
},
isAValidEvent(selectedKey) {
return (
!this.hasUserMention &&
!this.showCannedMenu &&
this.isFocused &&
isEditorHotKeyEnabled(this.uiSettings, selectedKey)
);
},
onPaste(e) {
const data = e.clipboardData.files;
if (!this.showRichContentEditor && data.length !== 0) {
@ -492,34 +621,6 @@ export default {
toggleCannedMenu(value) {
this.showCannedMenu = value;
},
handleKeyEvents(e) {
if (isEscape(e)) {
this.hideEmojiPicker();
this.hideMentions();
} else if (isEnter(e)) {
const hasSendOnEnterEnabled =
(this.showRichContentEditor &&
this.enterToSendEnabled &&
!this.hasUserMention &&
!this.showCannedMenu) ||
!this.showRichContentEditor;
const shouldSendMessage =
hasSendOnEnterEnabled && !hasPressedShift(e) && this.isFocused;
if (shouldSendMessage) {
e.preventDefault();
this.onSendReply();
}
} else if (hasPressedCommandPlusKKey(e)) {
this.openCommandBar();
}
},
openCommandBar() {
const ninja = document.querySelector('ninja-keys');
ninja.open();
},
toggleEnterToSend(enterToSendEnabled) {
this.updateUISettings({ enter_to_send_enabled: enterToSendEnabled });
},
openWhatsappTemplateModal() {
this.showWhatsAppTemplatesModal = true;
},
@ -559,11 +660,13 @@ export default {
newMessage += '\n\n' + this.messageSignature;
}
const messagePayload = this.getMessagePayload(newMessage);
this.clearMessage();
if (!this.isPrivate) {
this.clearEmailField();
}
this.sendMessage(messagePayload);
this.clearMessage();
this.hideEmojiPicker();
this.$emit('update:popoutReplyBox', false);
}
@ -575,6 +678,7 @@ export default {
messagePayload
);
bus.$emit(BUS_EVENTS.SCROLL_TO_MESSAGE);
this.removeFromDraft();
} catch (error) {
const errorMessage =
error?.response?.data?.error || this.$t('CONVERSATION.MESSAGE_ERROR');
@ -595,7 +699,6 @@ export default {
},
setReplyMode(mode = REPLY_EDITOR_MODES.REPLY) {
const { can_reply: canReply } = this.currentChat;
if (canReply || this.isAWhatsAppChannel) this.replyType = mode;
if (this.showRichContentEditor) {
if (this.isRecordingAudio) {
@ -654,6 +757,7 @@ export default {
},
onBlur() {
this.isFocused = false;
this.saveDraft(this.conversationIdByRoute, this.replyType);
},
onFocus() {
this.isFocused = true;
@ -879,7 +983,7 @@ export default {
&::before {
transform: rotate(0deg);
left: var(--space-half);
left: var(--space-smaller);
bottom: var(--space-minus-slab);
}
}

View file

@ -1,49 +1,160 @@
<template>
<mention-box :items="items" @mention-select="handleMentionClick" />
<ul
v-if="items.length"
class="vertical dropdown menu mention--box"
:style="{ top: getTopSpacing() + 'rem' }"
:class="{ 'with-bottom-border': items.length <= 4 }"
>
<li
v-for="(agent, index) in items"
:id="`mention-item-${index}`"
:key="agent.id"
:class="{ active: index === selectedIndex }"
@click="onAgentSelect(index)"
@mouseover="onHover(index)"
>
<div class="mention--thumbnail">
<woot-thumbnail
:src="agent.thumbnail"
:username="agent.name"
size="32px"
/>
</div>
<div class="mention--metadata text-truncate">
<h5 class="text-block-title mention--user-name text-truncate">
{{ agent.name }}
</h5>
<div class="text-truncate mention--email text-truncate">
{{ agent.email }}
</div>
</div>
</li>
</ul>
</template>
<script>
import { mapGetters } from 'vuex';
import MentionBox from '../mentions/MentionBox.vue';
import mentionSelectionKeyboardMixin from '../mentions/mentionSelectionKeyboardMixin';
export default {
components: { MentionBox },
mixins: [mentionSelectionKeyboardMixin],
props: {
searchKey: {
type: String,
default: '',
},
},
data() {
return { selectedIndex: 0 };
},
computed: {
...mapGetters({
agents: 'agents/getVerifiedAgents',
}),
...mapGetters({ agents: 'agents/getVerifiedAgents' }),
items() {
if (!this.searchKey) {
return this.agents.map(agent => ({
label: agent.name,
key: agent.id,
description: agent.email,
}));
return this.agents;
}
return this.agents
.filter(agent =>
agent.name
.toLocaleLowerCase()
.includes(this.searchKey.toLocaleLowerCase())
)
.map(agent => ({
label: agent.name,
key: agent.id,
description: agent.email,
}));
return this.agents.filter(agent =>
agent.name
.toLocaleLowerCase()
.includes(this.searchKey.toLocaleLowerCase())
);
},
},
watch: {
items(newListOfAgents) {
if (newListOfAgents.length < this.selectedIndex + 1) {
this.selectedIndex = 0;
}
},
},
methods: {
handleMentionClick(item = {}) {
this.$emit('click', item);
getTopSpacing() {
if (this.items.length <= 4) {
return -(this.items.length * 5 + 1.7);
}
return -20;
},
handleKeyboardEvent(e) {
this.processKeyDownEvent(e);
this.$el.scrollTop = 50 * this.selectedIndex;
},
onHover(index) {
this.selectedIndex = index;
},
onAgentSelect(index) {
this.selectedIndex = index;
this.onSelect();
},
onSelect() {
this.$emit('click', this.items[this.selectedIndex]);
},
},
};
</script>
<style scoped lang="scss">
.mention--box {
background: var(--white);
border-top: 1px solid var(--color-border);
font-size: var(--font-size-small);
left: 0;
line-height: 1.2;
max-height: 20rem;
overflow: auto;
padding: var(--space-small) var(--space-small) 0 var(--space-small);
position: absolute;
width: 100%;
z-index: 100;
&.with-bottom-border {
border-bottom: var(--space-small) solid var(--white);
li {
&:last-child {
margin-bottom: 0;
}
}
}
li {
align-items: center;
border-radius: var(--border-radius-normal);
display: flex;
padding: var(--space-small);
&.active {
background: var(--s-50);
.mention--user-name {
color: var(--s-900);
}
.mention--email {
color: var(--s-800);
}
}
&:last-child {
margin-bottom: var(--space-small);
}
}
}
.mention--thumbnail {
margin-right: var(--space-small);
}
.mention--user-name {
margin-bottom: 0;
}
.mention--email {
color: var(--s-700);
font-size: var(--font-size-mini);
}
.mention--metadata {
flex: 1;
max-width: 100%;
}
</style>

View file

@ -141,9 +141,19 @@ export default {
assignableAgentsUiFlags: 'inboxAssignableAgents/getUIFlags',
}),
assignableAgents() {
return this.$store.getters['inboxAssignableAgents/getAssignableAgents'](
this.inboxId
);
return [
{
confirmed: true,
name: 'None',
id: null,
role: 'agent',
account_id: 0,
email: 'None',
},
...this.$store.getters['inboxAssignableAgents/getAssignableAgents'](
this.inboxId
),
];
},
},
mounted() {

View file

@ -57,7 +57,7 @@
</li>
</ul>
<div v-else class="agent-confirmation-container">
<p>
<p v-if="selectedAgent.id">
{{
$t('BULK_ACTION.ASSIGN_CONFIRMATION_LABEL', {
conversationCount,
@ -67,6 +67,15 @@
<strong>
{{ selectedAgent.name }}
</strong>
<span>?</span>
</p>
<p v-else>
{{
$t('BULK_ACTION.UNASSIGN_CONFIRMATION_LABEL', {
conversationCount,
conversationLabel,
})
}}
</p>
<div class="agent-confirmation-actions">
<woot-button
@ -82,7 +91,7 @@
:is-loading="uiFlags.isUpdating"
@click="submit"
>
{{ $t('BULK_ACTION.ASSIGN_LABEL') }}
{{ $t('BULK_ACTION.YES') }}
</woot-button>
</div>
</div>
@ -131,7 +140,17 @@ export default {
agent.name.toLowerCase().includes(this.query.toLowerCase())
);
}
return this.assignableAgents;
return [
{
confirmed: true,
name: 'None',
id: null,
role: 'agent',
account_id: 0,
email: 'None',
},
...this.assignableAgents,
];
},
assignableAgents() {
return this.$store.getters['inboxAssignableAgents/getAssignableAgents'](

View file

@ -181,7 +181,7 @@ export default {
color: var(--y-700);
font-size: var(--font-size-mini);
margin-top: var(--space-small);
padding: var(--space-half) var(--space-one);
padding: var(--space-smaller) var(--space-small);
}
.popover-animation-enter-active,

View file

@ -20,7 +20,9 @@
</template>
<script>
import mentionSelectionKeyboardMixin from './mentionSelectionKeyboardMixin';
export default {
mixins: [mentionSelectionKeyboardMixin],
props: {
items: {
type: Array,
@ -39,56 +41,25 @@ export default {
}
},
},
mounted() {
document.addEventListener('keydown', this.keyListener);
},
beforeDestroy() {
document.removeEventListener('keydown', this.keyListener);
},
methods: {
getTopPadding() {
if (this.items.length <= 4) {
return -(this.items.length * 2.8 + 1.7);
return -(this.items.length * 2.9 + 1.7);
}
return -14;
},
isUp(e) {
return e.keyCode === 38 || (e.ctrlKey && e.keyCode === 80); // UP, Ctrl-P
},
isDown(e) {
return e.keyCode === 40 || (e.ctrlKey && e.keyCode === 78); // DOWN, Ctrl-N
},
isEnter(e) {
return e.keyCode === 13;
},
keyListener(e) {
if (this.isUp(e)) {
if (!this.selectedIndex) {
this.selectedIndex = this.items.length - 1;
} else {
this.selectedIndex -= 1;
}
}
if (this.isDown(e)) {
if (this.selectedIndex === this.items.length - 1) {
this.selectedIndex = 0;
} else {
this.selectedIndex += 1;
}
}
if (this.isEnter(e)) {
this.onMentionSelect();
}
this.$el.scrollTop = 28 * this.selectedIndex;
handleKeyboardEvent(e) {
this.processKeyDownEvent(e);
this.$el.scrollTop = 29 * this.selectedIndex;
},
onHover(index) {
this.selectedIndex = index;
},
onListItemSelection(index) {
this.selectedIndex = index;
this.onMentionSelect();
this.onSelect();
},
onMentionSelect() {
onSelect() {
this.$emit('mention-select', this.items[this.selectedIndex]);
},
},

View file

@ -0,0 +1,39 @@
import { buildHotKeys } from 'shared/helpers/KeyboardHelpers';
export default {
mounted() {
document.addEventListener('keydown', this.handleKeyboardEvent);
},
beforeDestroy() {
document.removeEventListener('keydown', this.handleKeyboardEvent);
},
methods: {
moveSelectionUp() {
if (!this.selectedIndex) {
this.selectedIndex = this.items.length - 1;
} else {
this.selectedIndex -= 1;
}
},
moveSelectionDown() {
if (this.selectedIndex === this.items.length - 1) {
this.selectedIndex = 0;
} else {
this.selectedIndex += 1;
}
},
processKeyDownEvent(e) {
const keyPattern = buildHotKeys(e);
if (['arrowup', 'ctrl+p'].includes(keyPattern)) {
this.moveSelectionUp();
e.preventDefault();
} else if (['arrowdown', 'ctrl+n'].includes(keyPattern)) {
this.moveSelectionDown();
e.preventDefault();
} else if (keyPattern === 'enter') {
this.onSelect();
e.preventDefault();
}
},
},
};

View file

@ -0,0 +1,64 @@
import mentionSelectionKeyboardMixin from '../mentionSelectionKeyboardMixin';
import { shallowMount } from '@vue/test-utils';
const buildComponent = ({ data = {}, methods = {} }) => ({
render() {},
data() {
return data;
},
methods,
mixins: [mentionSelectionKeyboardMixin],
});
describe('mentionSelectionKeyboardMixin', () => {
test('register listeners', () => {
jest.spyOn(document, 'addEventListener');
const Component = buildComponent({});
shallowMount(Component);
// undefined expected as the method is not defined in the component
expect(document.addEventListener).toHaveBeenCalledWith(
'keydown',
undefined
);
});
test('processKeyDownEvent updates index on arrow up', () => {
const Component = buildComponent({
data: { selectedIndex: 0, items: [1, 2, 3] },
});
const wrapper = shallowMount(Component);
wrapper.vm.processKeyDownEvent({
ctrlKey: true,
key: 'p',
preventDefault: jest.fn(),
});
expect(wrapper.vm.selectedIndex).toBe(2);
});
test('processKeyDownEvent updates index on arrow down', () => {
const Component = buildComponent({
data: { selectedIndex: 0, items: [1, 2, 3] },
});
const wrapper = shallowMount(Component);
wrapper.vm.processKeyDownEvent({
key: 'ArrowDown',
preventDefault: jest.fn(),
});
expect(wrapper.vm.selectedIndex).toBe(1);
});
test('processKeyDownEvent calls select methods on Enter Key', () => {
const onSelectMockFn = jest.fn();
const Component = buildComponent({
data: { selectedIndex: 0, items: [1, 2, 3] },
methods: { onSelect: () => onSelectMockFn('enterKey pressed') },
});
const wrapper = shallowMount(Component);
wrapper.vm.processKeyDownEvent({
key: 'Enter',
preventDefault: jest.fn(),
});
expect(onSelectMockFn).toHaveBeenCalledWith('enterKey pressed');
wrapper.vm.onSelect();
});
});

View file

@ -0,0 +1,13 @@
export const FEATURE_FLAGS = {
AGENT_BOTS: 'agent_bots',
AGENT_MANAGEMENT: 'agent_management',
AUTOMATIONS: 'automations',
CANNED_RESPONSES: 'canned_responses',
CUSTOM_ATTRIBUTES: 'custom_attributes',
INBOX_MANAGEMENT: 'inbox_management',
INTEGRATIONS: 'integrations',
LABELS: 'labels',
MACROS: 'macros',
TEAM_MANAGEMENT: 'team_management',
VOICE_RECORDER: 'voice_recorder',
};

View file

@ -6,7 +6,7 @@ export const frontendURL = (path, params) => {
};
const getSSOAccountPath = ({ ssoAccountId, user }) => {
const { accounts = [] } = user || {};
const { accounts = [], account_id = null } = user || {};
const ssoAccount = accounts.find(
account => account.id === Number(ssoAccountId)
);
@ -14,7 +14,9 @@ const getSSOAccountPath = ({ ssoAccountId, user }) => {
if (ssoAccount) {
accountPath = `accounts/${ssoAccountId}`;
} else if (accounts.length) {
accountPath = `accounts/${accounts[0].id}`;
// If the account id is not found, redirect to the first account
const accountId = account_id || accounts[0].id;
accountPath = `accounts/${accountId}`;
}
return accountPath;
};

View file

@ -1,6 +1,7 @@
export const LOCAL_STORAGE_KEYS = {
DISMISSED_UPDATES: 'dismissedUpdates',
WIDGET_BUILDER: 'widgetBubble_',
DRAFT_MESSAGES: 'draftMessages',
};
export const LocalStorage = {

View file

@ -2,8 +2,8 @@
"CONVERSATION": {
"SELECT_A_CONVERSATION": "الرجاء اختيار محادثة من قائمة المحادثات",
"CSAT_REPLY_MESSAGE": "الرجاء تقييم المحادثة",
"404": "Sorry, we cannot find the conversation. Please try again",
"SWITCH_VIEW_LAYOUT": "Switch the layout",
"404": "عذراً، لا يمكننا العثور على المحادثة. الرجاء المحاولة مرة أخرى",
"SWITCH_VIEW_LAYOUT": "تبديل التصميم",
"DASHBOARD_APP_TAB_MESSAGES": "الرسائل",
"UNVERIFIED_SESSION": "لم يتم التحقق من هوية هذا المستخدم",
"NO_MESSAGE_1": "لا توجد رسائل بعد من العملاء في صندوق الوارد الخاص بك.",
@ -63,30 +63,30 @@
},
"CARD_CONTEXT_MENU": {
"PENDING": "تحديد كمعلق",
"RESOLVED": "Mark as resolved",
"RESOLVED": "تحديد كمحلولة",
"REOPEN": "إعادة فتح المحادثة",
"SNOOZE": {
"TITLE": "Snooze",
"TITLE": "غفوة",
"NEXT_REPLY": "حتى الرد القادم",
"TOMORROW": "حتى الغد",
"NEXT_WEEK": "حتى الأسبوع القادم"
},
"ASSIGN_AGENT": "Assign agent",
"ASSIGN_LABEL": "Assign label",
"AGENTS_LOADING": "Loading agents...",
"ASSIGN_TEAM": "Assign team",
"ASSIGN_AGENT": "تعيين وكيل",
"ASSIGN_LABEL": "إضافة وسم",
"AGENTS_LOADING": "جاري تحميل الوكلاء...",
"ASSIGN_TEAM": "تعيين فريق",
"API": {
"AGENT_ASSIGNMENT": {
"SUCCESFUL": "Conversation id %{conversationId} assigned to \"%{agentName}\"",
"FAILED": "Couldn't assign agent. Please try again."
"SUCCESFUL": "معرف المحادثة %{conversationId} تم تعيينه ل \"%{agentName}\"",
"FAILED": "تعذر تعيين الوكيل. الرجاء المحاولة مرة أخرى."
},
"LABEL_ASSIGNMENT": {
"SUCCESFUL": "Assigned label #%{labelName} to conversation id %{conversationId}",
"FAILED": "Couldn't assign label. Please try again."
"SUCCESFUL": "تعيين تسمية #%{labelName} لمعرف المحادثة %{conversationId}",
"FAILED": "تعذر تعيين التسمية. الرجاء المحاولة مرة أخرى."
},
"TEAM_ASSIGNMENT": {
"SUCCESFUL": "Assigned team \"%{team}\" to conversation id %{conversationId}",
"FAILED": "Couldn't assign team. Please try again."
"SUCCESFUL": "الفريق المعين \"%{team}\" لمعرف المحادثة %{conversationId}",
"FAILED": "تعذر تعيين الفريق. الرجاء المحاولة مرة أخرى."
}
}
},
@ -111,7 +111,6 @@
"TIP_AUDIORECORDER_ICON": "تسجيل الصوت",
"TIP_AUDIORECORDER_PERMISSION": "السماح بالوصول إلى الصوت",
"TIP_AUDIORECORDER_ERROR": "تعذر فتح الصوت",
"ENTER_TO_SEND": "زر الإدخل للإرسال",
"DRAG_DROP": "اسحب و أسقط هنا للإرفاق",
"START_AUDIO_RECORDING": "بدء التسجيل الصوتي",
"STOP_AUDIO_RECORDING": "إيقاف التسجيل الصوتي",
@ -132,13 +131,13 @@
},
"VISIBLE_TO_AGENTS": "ملاحظة خاصة: مرئية فقط لأعضاء فريق العمل والموظفين",
"CHANGE_STATUS": "تم تغيير حالة المحادثة",
"CHANGE_STATUS_FAILED": "Conversation status change failed",
"CHANGE_STATUS_FAILED": "فشل تغيير حالة المحادثة",
"CHANGE_AGENT": "تم تغيير الموظف الذي تم إحالة المحادثة إليه",
"CHANGE_AGENT_FAILED": "Assignee change failed",
"ASSIGN_LABEL_SUCCESFUL": "Label assigned successfully",
"ASSIGN_LABEL_FAILED": "Label assignment failed",
"CHANGE_AGENT_FAILED": "فشل تغيير المحال إليه",
"ASSIGN_LABEL_SUCCESFUL": "تم تعيين الوسم بنجاح",
"ASSIGN_LABEL_FAILED": "فشل تعيين الوسم",
"CHANGE_TEAM": "تم تغيير فريق المحادثة",
"FILE_SIZE_LIMIT": "File exceeds the {MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE} MB attachment limit",
"FILE_SIZE_LIMIT": "الملف يتجاوز حد المرفق {MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE} ميغابايت",
"MESSAGE_ERROR": "غير قادر على إرسال هذه الرسالة، الرجاء المحاولة مرة أخرى لاحقاً",
"SENT_BY": "أرسلت بواسطة:",
"BOT": "رد آلي",
@ -151,7 +150,8 @@
},
"CONTEXT_MENU": {
"COPY": "نسخ",
"DELETE": "حذف"
"DELETE": "حذف",
"CREATE_A_CANNED_RESPONSE": "إضافة إلى الردود السريعة"
}
},
"EMAIL_TRANSCRIPT": {

View file

@ -6,98 +6,98 @@
"SETTINGS_BUTTON": "الإعدادات",
"NEW_BUTTON": "مقالة جديدة",
"DROPDOWN_OPTIONS": {
"PUBLISHED": "Published",
"DRAFT": "Draft",
"ARCHIVED": "Archived"
"PUBLISHED": "نُشرت",
"DRAFT": "مسودة",
"ARCHIVED": "أرشفة"
},
"TITLES": {
"ALL_ARTICLES": "All Articles",
"MINE": "My Articles",
"DRAFT": "Draft Articles",
"ARCHIVED": "Archived Articles"
"ALL_ARTICLES": "جميع المقالات",
"MINE": "مقالاتي",
"DRAFT": "مقالات مسودة",
"ARCHIVED": "المقالات المؤرشفة"
}
},
"EDIT_HEADER": {
"ALL_ARTICLES": "All Articles",
"ALL_ARTICLES": "جميع المقالات",
"PUBLISH_BUTTON": "نشر",
"MOVE_TO_ARCHIVE_BUTTON": "Move to archived",
"MOVE_TO_ARCHIVE_BUTTON": "نقل إلى الأرشيف",
"PREVIEW": "معاينة",
"ADD_TRANSLATION": "إضافة ترجمة",
"OPEN_SIDEBAR": "فتح الشريط الجانبي",
"CLOSE_SIDEBAR": "إغلاق الشريط الجانبي",
"SAVING": "Saving...",
"SAVED": "Saved"
"SAVING": "جاري الحفظ...",
"SAVED": "تم الحفظ"
},
"ARTICLE_SETTINGS": {
"TITLE": "Article Settings",
"TITLE": "إعدادات المقالة",
"FORM": {
"CATEGORY": {
"LABEL": "الفئة",
"TITLE": "Select category",
"PLACEHOLDER": "Select category",
"NO_RESULT": "No category found",
"SEARCH_PLACEHOLDER": "Search category"
"TITLE": "اختر الفئة",
"PLACEHOLDER": "اختر الفئة",
"NO_RESULT": "لم يتم العثور على فئة",
"SEARCH_PLACEHOLDER": "البحث عن فئة"
},
"AUTHOR": {
"LABEL": "Author",
"TITLE": "Select author",
"PLACEHOLDER": "Select author",
"NO_RESULT": "No authors found",
"SEARCH_PLACEHOLDER": "Search author"
"LABEL": "المؤلف",
"TITLE": "اختر المؤلف",
"PLACEHOLDER": "اختر المؤلف",
"NO_RESULT": "لم يتم العثور على مؤلفين",
"SEARCH_PLACEHOLDER": "البحث عن المؤلف"
},
"META_TITLE": {
"LABEL": "Meta title",
"PLACEHOLDER": "Add a meta title"
"LABEL": "العنوان الوصفي",
"PLACEHOLDER": "اضافة عنوان وصفي"
},
"META_DESCRIPTION": {
"LABEL": "Meta description",
"PLACEHOLDER": "Add your meta description for better SEO results..."
"LABEL": "وصف التعريف",
"PLACEHOLDER": "أضف وصفك للحصول على أفضل نتائج SEO..."
},
"META_TAGS": {
"LABEL": "Meta tags",
"PLACEHOLDER": "Add meta tags separated by comma..."
"LABEL": "علامات الوصف",
"PLACEHOLDER": "إضافة وسوم ميتا مفصولة بفاصلة..."
}
},
"BUTTONS": {
"ARCHIVE": "Archive article",
"DELETE": "Delete article"
"ARCHIVE": "المقالات المؤرشفة",
"DELETE": "حذف المقال"
}
},
"PORTAL": {
"HEADER": "Portals",
"DEFAULT": "Default",
"NEW_BUTTON": "New Portal",
"HEADER": "الصفحات",
"DEFAULT": "افتراضي",
"NEW_BUTTON": "بوابة جديدة",
"ACTIVE_BADGE": "مفعل",
"CHOOSE_LOCALE_LABEL": "Choose a locale",
"LOADING_MESSAGE": "Loading portals...",
"ARTICLES_LABEL": "articles",
"NO_PORTALS_MESSAGE": "There are no available portals",
"ADD_NEW_LOCALE": "Add a new locale",
"CHOOSE_LOCALE_LABEL": "اختر لغة محلية",
"LOADING_MESSAGE": "جاري تحميل البوابات ...",
"ARTICLES_LABEL": "المقالات",
"NO_PORTALS_MESSAGE": "لا توجد بوابات متاحة",
"ADD_NEW_LOCALE": "إضافة لغة جديدة",
"POPOVER": {
"TITLE": "Portals",
"PORTAL_SETTINGS": "Portal settings",
"SUBTITLE": "You have multiple portals and can have different locales for each portal.",
"TITLE": "الصفحات",
"PORTAL_SETTINGS": "إعدادات البوابة",
"SUBTITLE": "لديك بوابات متعددة ويمكن أن تحتوي على مواقع مختلفة لكل بوابة.",
"CANCEL_BUTTON_LABEL": "إلغاء",
"CHOOSE_LOCALE_BUTTON": "Choose Locale"
"CHOOSE_LOCALE_BUTTON": "اختر لغة محلية"
},
"PORTAL_SETTINGS": {
"LIST_ITEM": {
"HEADER": {
"COUNT_LABEL": "articles",
"ADD": "Add locale",
"VISIT": "Visit site",
"COUNT_LABEL": "المقالات",
"ADD": "إضافة لغة",
"VISIT": "زيارة الموقع",
"SETTINGS": "الإعدادات",
"DELETE": "حذف"
},
"PORTAL_CONFIG": {
"TITLE": "Portal Configurations",
"TITLE": "اعدادات البوابة",
"ITEMS": {
"NAME": "الاسم",
"DOMAIN": "Custom domain",
"SLUG": "Slug",
"TITLE": "Portal title",
"THEME": "Theme color",
"SUB_TEXT": "Portal sub text"
"DOMAIN": "نطاق مخصص",
"SLUG": "وصف مختصر",
"TITLE": "عنوان البوابة",
"THEME": "لون القالب",
"SUB_TEXT": "النص الفرعي للبوابة"
}
},
"AVAILABLE_LOCALES": {
@ -109,7 +109,7 @@
"CATEGORIES": "No. of categories",
"SWAP": "Swap",
"DELETE": "حذف",
"DEFAULT_LOCALE": "Default"
"DEFAULT_LOCALE": "افتراضي"
}
}
},
@ -152,7 +152,7 @@
"EDIT": "Edit category",
"DELETE": "Delete category"
},
"EMPTY_TEXT": "No categories found"
"EMPTY_TEXT": "لم يتم العثور على فئات"
}
},
"EDIT_BASIC_INFO": {
@ -206,113 +206,113 @@
"NAME": {
"LABEL": "الاسم",
"PLACEHOLDER": "Portal name",
"HELP_TEXT": "The name will be used in the public facing portal internally.",
"HELP_TEXT": "الاسم سيتم مشاهدتة من جميع من جميع زوار الصفحة.",
"ERROR": "الاسم مطلوب"
},
"SLUG": {
"LABEL": "Slug",
"PLACEHOLDER": "Portal slug for urls",
"ERROR": "Slug is required"
"LABEL": "وصف مختصر",
"PLACEHOLDER": "وصف مختصر لرابط البوابة",
"ERROR": "الوصف مطلوب"
},
"DOMAIN": {
"LABEL": "Custom Domain",
"PLACEHOLDER": "Portal custom domain",
"HELP_TEXT": "Add only If you want to use a custom domain for your portals.",
"ERROR": "Custom Domain is required"
"LABEL": "نطاق مخصص",
"PLACEHOLDER": "نطاق البوابة المخصص",
"HELP_TEXT": "أضف فقط إذا كنت ترغب في استخدام نطاق مخصص للبوابات الخاصة بك.",
"ERROR": "النطاق المخصص مطلوب"
},
"HOME_PAGE_LINK": {
"LABEL": "Home Page Link",
"PLACEHOLDER": "Portal home page link",
"HELP_TEXT": "The link used to return from the portal to the home page.",
"ERROR": "Home Page Link is required"
"LABEL": "رابط الصفحة الرئيسية",
"PLACEHOLDER": "رابط الصفحة الرئيسية للبوابة",
"HELP_TEXT": "الرابط المستخدم للعودة من البوابة إلى الصفحة الرئيسية.",
"ERROR": "رابط الصفحة الرئيسية مطلوب"
},
"THEME_COLOR": {
"LABEL": "Portal theme color",
"HELP_TEXT": "This color will show as the theme color for the portal."
"LABEL": "لون قالب البوابة",
"HELP_TEXT": "هذا اللون سيظهر كلون للبوابة."
},
"PAGE_TITLE": {
"LABEL": "Page Title",
"PLACEHOLDER": "Portal page title",
"HELP_TEXT": "The page title will be used in the public facing portal.",
"ERROR": "Page title is required"
"LABEL": "عنوان الصفحة",
"PLACEHOLDER": "عنوان البوابة",
"HELP_TEXT": "سيتم استخدام عنوان الصفحة في البوابة التي تواجه الجمهور.",
"ERROR": "العنوان مطلوب"
},
"HEADER_TEXT": {
"LABEL": "Header Text",
"PLACEHOLDER": "Portal header text",
"HELP_TEXT": "The Portal header text will be used in the public facing portal.",
"ERROR": "Portal header text is required"
"LABEL": "نص رأس الصفحة",
"PLACEHOLDER": "نص رأس البوابة",
"HELP_TEXT": "سيتم استخدام عنوان الصفحة في البوابة التي تواجه الجمهور.",
"ERROR": "نص رأس البوابة مطلوب"
},
"API": {
"SUCCESS_MESSAGE_FOR_BASIC": "Portal created successfully.",
"ERROR_MESSAGE_FOR_BASIC": "Couldn't create the portal. Try again.",
"SUCCESS_MESSAGE_FOR_UPDATE": "Portal updated successfully.",
"ERROR_MESSAGE_FOR_UPDATE": "Couldn't update the portal. Try again."
"SUCCESS_MESSAGE_FOR_BASIC": "تم إنشاء البوابة بنجاح.",
"ERROR_MESSAGE_FOR_BASIC": "تعذر إنشاء البوابة. حاول مرة أخرى.",
"SUCCESS_MESSAGE_FOR_UPDATE": "تم تحديث البوابة بنجاح.",
"ERROR_MESSAGE_FOR_UPDATE": "تعذر تحديث البوابة. حاول مرة أخرى."
}
},
"ADD_LOCALE": {
"TITLE": "Add a new locale",
"SUB_TITLE": "This adds a new locale to your available translation list.",
"PORTAL": "Portal",
"TITLE": "إضافة لغة جديدة",
"SUB_TITLE": "هذا يضيف لغة جديدة إلى قائمة الترجمة المتاحة لديك.",
"PORTAL": "البوابة",
"LOCALE": {
"LABEL": "Locale",
"PLACEHOLDER": "Choose a locale",
"ERROR": "Locale is required"
"LABEL": "اللغة",
"PLACEHOLDER": "اختر لغة محلية",
"ERROR": "اللغة مطلوبة"
},
"BUTTONS": {
"CREATE": "Create locale",
"CREATE": "إنشاء لغة",
"CANCEL": "إلغاء"
},
"API": {
"SUCCESS_MESSAGE": "Locale added successfully",
"ERROR_MESSAGE": "Unable to add locale. Try again."
"SUCCESS_MESSAGE": "تمت إضافة اللغة بنجاح",
"ERROR_MESSAGE": "غير قادر على إضافة اللغة . حاول مرة أخرى."
}
},
"CHANGE_DEFAULT_LOCALE": {
"API": {
"SUCCESS_MESSAGE": "Default locale updated successfully",
"ERROR_MESSAGE": "Unable to update default locale. Try again."
"SUCCESS_MESSAGE": "تم تحديث اللغة الغة الإفتراضية بنجاح",
"ERROR_MESSAGE": "غير قادر على تحديث اللغة الافتراضية. حاول مرة أخرى."
}
},
"DELETE_LOCALE": {
"API": {
"SUCCESS_MESSAGE": "Locale removed from portal successfully",
"ERROR_MESSAGE": "Unable to remove locale from portal. Try again."
"SUCCESS_MESSAGE": "تم إزالة اللغة من البوابة بنجاح",
"ERROR_MESSAGE": "غير قادر على إزالة اللغة من البوابة. حاول مرة أخرى."
}
}
},
"TABLE": {
"LOADING_MESSAGE": "Loading articles...",
"404": "No articles matches your search 🔍",
"NO_ARTICLES": "There are no available articles",
"LOADING_MESSAGE": "جاري تحميل المقالات...",
"404": "لا توجد مقالات تطابق بحثك 🔍",
"NO_ARTICLES": "لا توجد مقالات متوفرة",
"HEADERS": {
"TITLE": "العنوان",
"CATEGORY": "الفئة",
"READ_COUNT": "Read count",
"READ_COUNT": "عدد القراءات",
"STATUS": "الحالة",
"LAST_EDITED": "Last edited"
"LAST_EDITED": "آخر تعديل"
},
"COLUMNS": {
"BY": "بواسطة"
}
},
"EDIT_ARTICLE": {
"LOADING": "Loading article...",
"TITLE_PLACEHOLDER": "Article title goes here",
"CONTENT_PLACEHOLDER": "Write your article here",
"LOADING": "جاري تحميل المقالات...",
"TITLE_PLACEHOLDER": "عنوان المقالة يذهب هنا",
"CONTENT_PLACEHOLDER": "اكتب مقالك هنا",
"API": {
"ERROR": "Error while saving article"
"ERROR": "حدث خطأ أثناء حفظ المقالة"
}
},
"PUBLISH_ARTICLE": {
"API": {
"ERROR": "Error while publishing article",
"SUCCESS": "Article publishied successfully"
"ERROR": "حدث خطأ أثناء نشر المقالة",
"SUCCESS": "تم نشر المقالة بنجاح"
}
},
"ARCHIVE_ARTICLE": {
"API": {
"ERROR": "Error while archiving article",
"SUCCESS": "Article archived successfully"
"ERROR": "حدث خطأ أثناء نشر المقالة",
"SUCCESS": "تم أرشفة المقالة بنجاح"
}
},
"DELETE_ARTICLE": {

View file

@ -414,7 +414,7 @@
"CAMPAIGN": "الحملات",
"PRE_CHAT_FORM": "نموذج ما قبل الدردشة",
"BUSINESS_HOURS": "ساعات العمل",
"WIDGET_BUILDER": "Widget Builder"
"WIDGET_BUILDER": "منشئ اللايف شات"
},
"SETTINGS": "الإعدادات",
"FEATURES": {
@ -579,10 +579,10 @@
"WIDGET_BUILDER": {
"WIDGET_OPTIONS": {
"AVATAR": {
"LABEL": "Website Avatar",
"LABEL": "صورة الموقع",
"DELETE": {
"API": {
"SUCCESS_MESSAGE": "Avatar deleted successfully",
"SUCCESS_MESSAGE": "الصورة الرمزية حذفت بنجاح",
"ERROR_MESSAGE": "حدث خطأ، الرجاء المحاولة مرة أخرى"
}
}
@ -590,53 +590,53 @@
"WEBSITE_NAME": {
"LABEL": "اسم الموقع",
"PLACE_HOLDER": "أدخل اسم موقع الويب الخاص بك (مثال: Acme Inc)",
"ERROR": "Please enter a valid website name"
"ERROR": "الرجاء إدخال اسم موقع صالح"
},
"WELCOME_HEADING": {
"LABEL": "العنوان الترحيبي",
"PLACE_HOLDER": "Hi there!"
"PLACE_HOLDER": "مرحبا بك!"
},
"WELCOME_TAGLINE": {
"LABEL": "افتتاحية الترحيب",
"PLACE_HOLDER": "نحن نجعل من السهل عليك التواصل معنا. اسألنا أي شيء، أو قم بمشاركتنا ملاحظاتك."
},
"REPLY_TIME": {
"LABEL": "Reply Time",
"LABEL": "وقت الرد",
"IN_A_FEW_MINUTES": "في غضون دقائق قليلة",
"IN_A_FEW_HOURS": "في غضون ساعات قليلة",
"IN_A_DAY": "خلال يوم"
},
"WIDGET_COLOR_LABEL": "لون صندوق الدردشة",
"WIDGET_BUBBLE_POSITION_LABEL": "Widget Bubble Position",
"WIDGET_BUBBLE_TYPE_LABEL": "Widget Bubble Type",
"WIDGET_BUBBLE_POSITION_LABEL": "موقع شعار اللايف شات",
"WIDGET_BUBBLE_TYPE_LABEL": "شكل عرض اللايف شات",
"WIDGET_BUBBLE_LAUNCHER_TITLE": {
"DEFAULT": "تحدث الينا",
"LABEL": "Widget Bubble Launcher Title",
"LABEL": "عنوان ايقونة اللايف شات",
"PLACE_HOLDER": "تحدث الينا"
},
"UPDATE": {
"BUTTON_TEXT": "Update Widget Settings",
"BUTTON_TEXT": "تحديث إعدادات اللايف شات",
"API": {
"SUCCESS_MESSAGE": "Widget settings updated successfully",
"ERROR_MESSAGE": "Unable to update widget settings"
"SUCCESS_MESSAGE": "تم تحديث إعدادت اللايف شات بنجاح",
"ERROR_MESSAGE": "غير قادر على تحديث إعدادات اللايف شات"
}
},
"WIDGET_VIEW_OPTION": {
"PREVIEW": "معاينة",
"SCRIPT": "Script"
"SCRIPT": "النص"
},
"WIDGET_BUBBLE_POSITION": {
"LEFT": "Left",
"RIGHT": "Right"
"LEFT": "يسار",
"RIGHT": "يمين"
},
"WIDGET_BUBBLE_TYPE": {
"STANDARD": "Standard",
"EXPANDED_BUBBLE": "Expanded Bubble"
"STANDARD": "عادي",
"EXPANDED_BUBBLE": "ايقونت اللايف شات العرضية"
}
},
"WIDGET_SCREEN": {
"DEFAULT": "Default",
"CHAT": "Chat"
"DEFAULT": "افتراضي",
"CHAT": "محادثة"
},
"REPLY_TIME": {
"IN_A_FEW_MINUTES": "عادة نقوم بالرد خلال بضع دقائق",
@ -649,11 +649,11 @@
},
"BODY": {
"TEAM_AVAILABILITY": {
"ONLINE": "We are Online",
"ONLINE": "متواجدون لخدمتك",
"OFFLINE": "نحن بعيدون في الوقت الحالي"
},
"USER_MESSAGE": "Hi",
"AGENT_MESSAGE": "Hello"
"USER_MESSAGE": "مرحبا",
"AGENT_MESSAGE": "مرحبا"
},
"BRANDING_TEXT": "مدعوم بواسطة Chatwoot",
"SCRIPT_SETTINGS": "\n window.chatwootSettings = {options};"

View file

@ -19,6 +19,21 @@
"TITLE": "الملف الشخصي",
"NOTE": "عنوان بريدك الإلكتروني هو المعرف الخاص بك الذي ستستخدمه لتسجيل الدخول."
},
"SEND_MESSAGE": {
"TITLE": "المفتاح الرئيسي لإرسال الرسائل",
"NOTE": "يمكنك تحديد مفتاح سريع (إما Enter أو Cmd/Ctrl+Enter) استنادًا إلى تفضيلك للكتابة.",
"UPDATE_SUCCESS": "تم تحديث الإعدادات الخاصة بك بنجاح",
"CARD": {
"ENTER_KEY": {
"HEADING": "Enter (↵)",
"CONTENT": "إرسال الرسائل بالضغط على مفتاح الإدخال بدلاً من النقر على زر الإرسال."
},
"CMD_ENTER_KEY": {
"HEADING": "Cmd/Ctrl + Enter (<unk> + <unk> )",
"CONTENT": "إرسال الرسائل بالضغط على Cmd/Ctrl + إدخال المفتاح بدلاً من النقر على زر الإرسال."
}
}
},
"MESSAGE_SIGNATURE_SECTION": {
"TITLE": "توقيع الرسالة الشخصية",
"NOTE": "إنشاء توقيع رسالة شخصية يتم إضافتها إلى جميع الرسائل التي ترسلها من المنصة. استخدم محرر المحتوى الغني لإنشاء توقيع شديد التخصيص.",
@ -126,8 +141,8 @@
"TRAIL_BUTTON": "اشترك الآن",
"DELETED_USER": "حذف المستخدم",
"ACCOUNT_SUSPENDED": {
"TITLE": "Account Suspended",
"MESSAGE": "Your account is suspended. Please reach out to the support team for more information."
"TITLE": "تم تعليق الحساب",
"MESSAGE": "تم تعليق حسابك. يرجى الاتصال بفريق الدعم للمزيد من المعلومات."
}
},
"COMPONENTS": {
@ -175,7 +190,7 @@
"CUSTOM_ATTRIBUTES": "سمات مخصصة",
"AUTOMATION": "الأتمتة",
"TEAMS": "الفرق",
"BILLING": "Billing",
"BILLING": "الفواتير",
"CUSTOM_VIEWS_FOLDER": "المجلدات",
"CUSTOM_VIEWS_SEGMENTS": "الأجزاء",
"ALL_CONTACTS": "جميع جهات الاتصال",
@ -197,33 +212,33 @@
"REPORTS_OVERVIEW": "نظرة عامة",
"FACEBOOK_REAUTHORIZE": "انتهت صلاحية اتصال الفيسبوك الخاص بك، يرجى إعادة الاتصال بصفحة الفيسبوك الخاصة بك لمواصلة الخدمات",
"HELP_CENTER": {
"TITLE": "Help Center (Beta)",
"ALL_ARTICLES": "All Articles",
"MY_ARTICLES": "My Articles",
"DRAFT": "Draft",
"ARCHIVED": "Archived",
"TITLE": "مركز المساعدة (نسخة تجريبية)",
"ALL_ARTICLES": "جميع المقالات",
"MY_ARTICLES": "مقالاتي",
"DRAFT": "مسودة",
"ARCHIVED": "مؤرشفة",
"CATEGORY": "الفئة",
"CATEGORY_EMPTY_MESSAGE": "No categories found"
"CATEGORY_EMPTY_MESSAGE": "لم يتم العثور على فئات"
},
"DOCS": "Read docs"
"DOCS": "قراءة المستندات"
},
"BILLING_SETTINGS": {
"TITLE": "Billing",
"TITLE": "الفواتير",
"CURRENT_PLAN": {
"TITLE": "Current Plan",
"PLAN_NOTE": "You are currently subscribed to the **%{plan}** plan with **%{quantity}** licenses"
"TITLE": "الباقة الحالية",
"PLAN_NOTE": "أنت مشترك حاليا في باقة**%{plan}** مع تراخيص **%{quantity}**"
},
"MANAGE_SUBSCRIPTION": {
"TITLE": "Manage your subscription",
"DESCRIPTION": "View your previous invoices, edit your billing details, or cancel your subscription.",
"BUTTON_TXT": "Go to the billing portal"
"TITLE": "إدارة الاشتراك الخاص بك",
"DESCRIPTION": "عرض فواتيرك السابقة، تحرير تفاصيل الفوترة الخاصة بك، أو إلغاء اشتراكك.",
"BUTTON_TXT": "الذهاب إلى بوابة الفوترة"
},
"CHAT_WITH_US": {
"TITLE": "Need help?",
"DESCRIPTION": "Do you face any issues in billing? We are here to help.",
"TITLE": "تحتاج مساعدة؟",
"DESCRIPTION": "هل تواجه أي مشاكل في الفواتير؟ نحن هنا للمساعدة.",
"BUTTON_TXT": "تحدث الينا"
},
"NO_BILLING_USER": "Your billing account is being configured. Please refresh the page and try again."
"NO_BILLING_USER": "حساب الفوترة الخاص بك قيد الإعداد. الرجاء تحديث الصفحة وحاول مرة أخرى."
},
"CREATE_ACCOUNT": {
"NO_ACCOUNT_WARNING": "أوه! لم نتمكن من العثور على الحساب. الرجاء إنشاء حساب جديد للمتابعة.",

View file

@ -111,7 +111,6 @@
"TIP_AUDIORECORDER_ICON": "Record audio",
"TIP_AUDIORECORDER_PERMISSION": "Allow access to audio",
"TIP_AUDIORECORDER_ERROR": "Could not open the audio",
"ENTER_TO_SEND": "Enter to send",
"DRAG_DROP": "Drag and drop here to attach",
"START_AUDIO_RECORDING": "Start audio recording",
"STOP_AUDIO_RECORDING": "Stop audio recording",
@ -151,7 +150,8 @@
},
"CONTEXT_MENU": {
"COPY": "Copy",
"DELETE": "Изтрий"
"DELETE": "Изтрий",
"CREATE_A_CANNED_RESPONSE": "Add to canned responses"
}
},
"EMAIL_TRANSCRIPT": {

View file

@ -19,6 +19,21 @@
"TITLE": "Profile",
"NOTE": "Your email address is your identity and is used to log in."
},
"SEND_MESSAGE": {
"TITLE": "Hotkey to send messages",
"NOTE": "You can select a hotkey (either Enter or Cmd/Ctrl+Enter) based on your preference of writing.",
"UPDATE_SUCCESS": "Your settings have been updated successfully",
"CARD": {
"ENTER_KEY": {
"HEADING": "Enter (↵)",
"CONTENT": "Send messages by pressing Enter key instead of clicking the send button."
},
"CMD_ENTER_KEY": {
"HEADING": "Cmd/Ctrl + Enter (⌘ + ↵)",
"CONTENT": "Send messages by pressing Cmd/Ctrl + enter key instead of clicking the send button."
}
}
},
"MESSAGE_SIGNATURE_SECTION": {
"TITLE": "Personal message signature",
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",

View file

@ -111,7 +111,6 @@
"TIP_AUDIORECORDER_ICON": "Record audio",
"TIP_AUDIORECORDER_PERMISSION": "Allow access to audio",
"TIP_AUDIORECORDER_ERROR": "Could not open the audio",
"ENTER_TO_SEND": "Intro per enviar",
"DRAG_DROP": "Drag and drop here to attach",
"START_AUDIO_RECORDING": "Start audio recording",
"STOP_AUDIO_RECORDING": "Stop audio recording",
@ -151,7 +150,8 @@
},
"CONTEXT_MENU": {
"COPY": "Copia",
"DELETE": "Esborrar"
"DELETE": "Esborrar",
"CREATE_A_CANNED_RESPONSE": "Add to canned responses"
}
},
"EMAIL_TRANSCRIPT": {

View file

@ -19,6 +19,21 @@
"TITLE": "Perfil",
"NOTE": "La vostra adreça de correu electrònic és la vostra identitat i s'utilitza per iniciar la sessió."
},
"SEND_MESSAGE": {
"TITLE": "Hotkey to send messages",
"NOTE": "You can select a hotkey (either Enter or Cmd/Ctrl+Enter) based on your preference of writing.",
"UPDATE_SUCCESS": "Your settings have been updated successfully",
"CARD": {
"ENTER_KEY": {
"HEADING": "Enter (↵)",
"CONTENT": "Send messages by pressing Enter key instead of clicking the send button."
},
"CMD_ENTER_KEY": {
"HEADING": "Cmd/Ctrl + Enter (⌘ + ↵)",
"CONTENT": "Send messages by pressing Cmd/Ctrl + enter key instead of clicking the send button."
}
}
},
"MESSAGE_SIGNATURE_SECTION": {
"TITLE": "Personal message signature",
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",

View file

@ -111,7 +111,6 @@
"TIP_AUDIORECORDER_ICON": "Nahrát zvuk",
"TIP_AUDIORECORDER_PERMISSION": "Povolit přístup ke zvuku",
"TIP_AUDIORECORDER_ERROR": "Zvuk se nepodařilo otevřít",
"ENTER_TO_SEND": "Enterem odeslat",
"DRAG_DROP": "Přetažením sem připojíte",
"START_AUDIO_RECORDING": "Spustit nahrávání zvuku",
"STOP_AUDIO_RECORDING": "Zastavit nahrávání zvuku",
@ -151,7 +150,8 @@
},
"CONTEXT_MENU": {
"COPY": "Kopírovat",
"DELETE": "Vymazat"
"DELETE": "Vymazat",
"CREATE_A_CANNED_RESPONSE": "Add to canned responses"
}
},
"EMAIL_TRANSCRIPT": {

View file

@ -19,6 +19,21 @@
"TITLE": "Profil",
"NOTE": "Vaše e-mailová adresa je Vaše identita a používá se k přihlášení."
},
"SEND_MESSAGE": {
"TITLE": "Hotkey to send messages",
"NOTE": "You can select a hotkey (either Enter or Cmd/Ctrl+Enter) based on your preference of writing.",
"UPDATE_SUCCESS": "Your settings have been updated successfully",
"CARD": {
"ENTER_KEY": {
"HEADING": "Enter (↵)",
"CONTENT": "Send messages by pressing Enter key instead of clicking the send button."
},
"CMD_ENTER_KEY": {
"HEADING": "Cmd/Ctrl + Enter (⌘ + ↵)",
"CONTENT": "Send messages by pressing Cmd/Ctrl + enter key instead of clicking the send button."
}
}
},
"MESSAGE_SIGNATURE_SECTION": {
"TITLE": "Personal message signature",
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",

View file

@ -111,7 +111,6 @@
"TIP_AUDIORECORDER_ICON": "Optag lyd",
"TIP_AUDIORECORDER_PERMISSION": "Tillad adgang til lyd",
"TIP_AUDIORECORDER_ERROR": "Kunne ikke åbne lyden",
"ENTER_TO_SEND": "Indtast for at sende",
"DRAG_DROP": "Træk og slip her for at vedhæfte",
"START_AUDIO_RECORDING": "Start lydoptagelse",
"STOP_AUDIO_RECORDING": "Stop lydoptagelse",
@ -151,7 +150,8 @@
},
"CONTEXT_MENU": {
"COPY": "Kopiér",
"DELETE": "Slet"
"DELETE": "Slet",
"CREATE_A_CANNED_RESPONSE": "Add to canned responses"
}
},
"EMAIL_TRANSCRIPT": {

View file

@ -19,6 +19,21 @@
"TITLE": "Profil",
"NOTE": "Din e-mailadresse er din identitet og bruges til at logge ind."
},
"SEND_MESSAGE": {
"TITLE": "Hotkey to send messages",
"NOTE": "You can select a hotkey (either Enter or Cmd/Ctrl+Enter) based on your preference of writing.",
"UPDATE_SUCCESS": "Your settings have been updated successfully",
"CARD": {
"ENTER_KEY": {
"HEADING": "Enter (↵)",
"CONTENT": "Send messages by pressing Enter key instead of clicking the send button."
},
"CMD_ENTER_KEY": {
"HEADING": "Cmd/Ctrl + Enter (⌘ + ↵)",
"CONTENT": "Send messages by pressing Cmd/Ctrl + enter key instead of clicking the send button."
}
}
},
"MESSAGE_SIGNATURE_SECTION": {
"TITLE": "Personlig beskedsignatur",
"NOTE": "Opret en personlig besked signatur, der vil blive føjet til alle de meddelelser, du sender fra din e-mail indbakke. Brug den rige content editor til at oprette en meget personlig signatur.",

View file

@ -111,7 +111,6 @@
"TIP_AUDIORECORDER_ICON": "Audio aufzeichnen",
"TIP_AUDIORECORDER_PERMISSION": "Zugriff auf Audio zulassen",
"TIP_AUDIORECORDER_ERROR": "Audio konnte nicht geöffnet werden",
"ENTER_TO_SEND": "Zum senden ENTER drücken",
"DRAG_DROP": "Zum Anhängen hierher ziehen und ablegen",
"START_AUDIO_RECORDING": "Audioaufzeichnung starten",
"STOP_AUDIO_RECORDING": "Audioaufzeichnung stoppen",
@ -151,7 +150,8 @@
},
"CONTEXT_MENU": {
"COPY": "Kopieren",
"DELETE": "Löschen"
"DELETE": "Löschen",
"CREATE_A_CANNED_RESPONSE": "Zu vorgefertigten Antworten hinzufügen"
}
},
"EMAIL_TRANSCRIPT": {

View file

@ -19,6 +19,21 @@
"TITLE": "Profil",
"NOTE": "Ihre E-Mail-Adresse ist Ihre Identität und wird zum Anmelden verwendet."
},
"SEND_MESSAGE": {
"TITLE": "Hotkey zum Senden von Nachrichten",
"NOTE": "Sie können einen Hotkey (entweder Enter oder Cmd/Strg+Enter) basierend auf Ihrer Schreibpräferenz auswählen.",
"UPDATE_SUCCESS": "Ihre Einstellungen wurden erfolgreich aktualisiert",
"CARD": {
"ENTER_KEY": {
"HEADING": "Eingabe (↵)",
"CONTENT": "Senden Sie Nachrichten, indem Sie die Eingabetaste drücken, anstatt auf die Schaltfläche \"Senden\" zu klicken."
},
"CMD_ENTER_KEY": {
"HEADING": "Cmd/Strg + Enter (⌘ + ↵)",
"CONTENT": "Senden Sie Nachrichten, indem Sie Cmd/Strg + Eingabetaste drücken, anstatt auf die Schaltfläche „Senden“ zu klicken."
}
}
},
"MESSAGE_SIGNATURE_SECTION": {
"TITLE": "Persönliche Nachrichtensignatur",
"NOTE": "Erstellen Sie eine persönliche Nachrichtensignatur, die allen Nachrichten hinzugefügt wird, die Sie aus Ihrem E-Mail-Posteingang senden. Verwenden Sie den Rich-Content-Editor, um eine stark personalisierte Signatur zu erstellen.",

View file

@ -0,0 +1,5 @@
{
"AGENT_BOTS": {
"HEADER": "Bots"
}
}

View file

@ -3,8 +3,10 @@
"CONVERSATIONS_SELECTED": "%{conversationCount} σινομιλίες επιλέχθηκαν",
"AGENT_SELECT_LABEL": "Επιλογή πράκτορα",
"ASSIGN_CONFIRMATION_LABEL": "Είσαστε σίγουροι ότι θέλετε να αντιστοιχίσετε %{conversationCount} %{conversationLabel} στον",
"UNASSIGN_CONFIRMATION_LABEL": "Είσαστε σίγουροι ότι θέλετε να αφαιρέσετε την αντιστοίχιση %{conversationCount} %{conversationLabel} στον;",
"GO_BACK_LABEL": "Πίσω",
"ASSIGN_LABEL": "Αντιστοίχιση",
"YES": "Ναι",
"ASSIGN_AGENT_TOOLTIP": "Ανάθεση σε πράκτορα",
"ASSIGN_SUCCESFUL": "Οι σινομιλίες αντιστοιχήθηκαν επιτυχώς",
"ASSIGN_FAILED": "Αποτυχία στην αντιστοίχιση σινομιλιών, παρακαλώ δοκιμάστε αργότερα",

View file

@ -152,8 +152,8 @@
},
"DELETE_AVATAR": {
"API": {
"SUCCESS_MESSAGE": "Contact avatar deleted successfully",
"ERROR_MESSAGE": "Could not delete the contact avatar. Please try again later."
"SUCCESS_MESSAGE": "Το avatar της επαφής διαγράφηκε επιτυχώς",
"ERROR_MESSAGE": "Δεν ήταν δυνατή η διαγραφή του avatar της επαφής. Παρακαλώ προσπαθήστε ξανά αργότερα."
}
},
"SUCCESS_MESSAGE": "Η επαφή αποθηκεύτηκε με επιτυχία",

View file

@ -39,7 +39,7 @@
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
"CREATED_AT": "Δημιουργήθηκε στις",
"LAST_ACTIVITY": "Τελευταία Δραστηριότητα",
"REFERER_LINK": "Referrer link"
"REFERER_LINK": "Σύνδεσμος αναφοράς"
},
"GROUPS": {
"STANDARD_FILTERS": "Τυπικά Φίλτρα",

View file

@ -2,8 +2,8 @@
"CONVERSATION": {
"SELECT_A_CONVERSATION": "Παρακαλώ επιλέξτε συζήτηση από το αριστερό τμήμα",
"CSAT_REPLY_MESSAGE": "Παρακαλώ αξιολογήστε τη συνομιλία",
"404": "Sorry, we cannot find the conversation. Please try again",
"SWITCH_VIEW_LAYOUT": "Switch the layout",
"404": "Λυπούμαστε, δεν μπορούμε να βρούμε την συνομιλία. Παρακαλώ προσπαθήστε ξανά",
"SWITCH_VIEW_LAYOUT": "Εναλλαγή διάταξης",
"DASHBOARD_APP_TAB_MESSAGES": "Μηνύματα",
"UNVERIFIED_SESSION": "Η ταυτότητα αυτού του χρήστη δεν επαληθεύεται",
"NO_MESSAGE_1": "Ωχ ωχ! Φαίνεται ότι δεν υπάρχουν μηνύματα από τους πελάτες στα εισερχόμενά σας.",
@ -63,30 +63,30 @@
},
"CARD_CONTEXT_MENU": {
"PENDING": "Σήμανση ως εκκρεμής",
"RESOLVED": "Mark as resolved",
"RESOLVED": "Σήμανση ως επιλυμένου",
"REOPEN": "Άνοιγμα συνομιλίας",
"SNOOZE": {
"TITLE": "Snooze",
"TITLE": "Αναβολή",
"NEXT_REPLY": "Μέχρι την επόμενη απάντηση",
"TOMORROW": "Μέχρι αύριο",
"NEXT_WEEK": "Έως την επόμενη εβδομάδα"
},
"ASSIGN_AGENT": "Assign agent",
"ASSIGN_LABEL": "Assign label",
"AGENTS_LOADING": "Loading agents...",
"ASSIGN_TEAM": "Assign team",
"ASSIGN_AGENT": "Ανάθεση σε πράκτορα",
"ASSIGN_LABEL": "Εκχώρηση ετικέτας",
"AGENTS_LOADING": "Φόρτωση πρακτόρων...",
"ASSIGN_TEAM": "Ανάθεση ομάδας",
"API": {
"AGENT_ASSIGNMENT": {
"SUCCESFUL": "Conversation id %{conversationId} assigned to \"%{agentName}\"",
"FAILED": "Couldn't assign agent. Please try again."
"SUCCESFUL": "Η συνομιλία με αριθμό %{conversationId} ανατέθηκε στον \"%{agentName}\"",
"FAILED": "Αδυναμία αντιστοίχισης σε πράκτορα. Παρακαλώ δοκιμάστε ξανά."
},
"LABEL_ASSIGNMENT": {
"SUCCESFUL": "Assigned label #%{labelName} to conversation id %{conversationId}",
"FAILED": "Couldn't assign label. Please try again."
"SUCCESFUL": "Εκχώρηση ετικέτας #%{labelName} στην συνομιλία με αριθμό %{conversationId}",
"FAILED": "Αποτυχία στην εκχώρηση ετικέτας, παρακαλώ δοκιμάστε αργότερα."
},
"TEAM_ASSIGNMENT": {
"SUCCESFUL": "Assigned team \"%{team}\" to conversation id %{conversationId}",
"FAILED": "Couldn't assign team. Please try again."
"SUCCESFUL": "Η συνομιλία με αριθμό %{conversationId} ανατέθηκε στην ομάδα \"%{team}\"",
"FAILED": "Αδυναμία αντιστοίχισης ομάδας. Παρακαλώ δοκιμάστε ξανά."
}
}
},
@ -111,7 +111,6 @@
"TIP_AUDIORECORDER_ICON": "Εγγραφή ήχου",
"TIP_AUDIORECORDER_PERMISSION": "Να επιτρέπεται η πρόσβαση στον ήχο",
"TIP_AUDIORECORDER_ERROR": "Αδυναμία ανοίγματος ήχου",
"ENTER_TO_SEND": "Εισαγωγή για αποστολή",
"DRAG_DROP": "Σύρετε και αφήστε εδώ για επισύναψη",
"START_AUDIO_RECORDING": "Έναρξη ηχογράφησης",
"STOP_AUDIO_RECORDING": "Διακοπή ηχογράφησης",
@ -132,13 +131,13 @@
},
"VISIBLE_TO_AGENTS": "Ιδιωτική Σημείωση: Ορατή μόνο σε σας και την ομάδα σας",
"CHANGE_STATUS": "Η κατάσταση της συνομιλίας άλλαξε",
"CHANGE_STATUS_FAILED": "Conversation status change failed",
"CHANGE_STATUS_FAILED": "Η αλλαγή κατάστασης συνομιλίας απέτυχε",
"CHANGE_AGENT": "Η εκπροσώπηση για την συνομιλία άλλαξε",
"CHANGE_AGENT_FAILED": "Assignee change failed",
"ASSIGN_LABEL_SUCCESFUL": "Label assigned successfully",
"ASSIGN_LABEL_FAILED": "Label assignment failed",
"CHANGE_AGENT_FAILED": "Η αλλαγή της ανάθεσης απέτυχε",
"ASSIGN_LABEL_SUCCESFUL": "Επιτυχής εκχώρηση ετικέτας",
"ASSIGN_LABEL_FAILED": "Η εκχώρηση ετικέτας απέτυχε",
"CHANGE_TEAM": "Η ομάδα συνομιλίας άλλαξε",
"FILE_SIZE_LIMIT": "File exceeds the {MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE} MB attachment limit",
"FILE_SIZE_LIMIT": "Το αρχείο υπερβαίνει το όριο συνημμένου {MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE}",
"MESSAGE_ERROR": "Δεν είναι δυνατή η αποστολή του μηνύματος, παρακαλώ προσπαθήστε ξανά αργότερα",
"SENT_BY": "Αποστολή από:",
"BOT": "Bot",
@ -151,7 +150,8 @@
},
"CONTEXT_MENU": {
"COPY": "Αντιγραφή",
"DELETE": "Διαγραφή"
"DELETE": "Διαγραφή",
"CREATE_A_CANNED_RESPONSE": "Προσθήκη στις έτοιμες απαντήσεις"
}
},
"EMAIL_TRANSCRIPT": {

View file

@ -1,409 +1,409 @@
{
"HELP_CENTER": {
"HEADER": {
"FILTER": "Filter by",
"SORT": "Sort by",
"FILTER": "Φιλτράρισμα κατά",
"SORT": "Ταξινόμηση κατά",
"SETTINGS_BUTTON": "Ρυθμίσεις",
"NEW_BUTTON": "New Article",
"NEW_BUTTON": "Νέο Άρθρο",
"DROPDOWN_OPTIONS": {
"PUBLISHED": "Published",
"DRAFT": "Draft",
"ARCHIVED": "Archived"
"PUBLISHED": "Δημοσιευμένο",
"DRAFT": "Πρόχειρο",
"ARCHIVED": "Αρχειοθετημένο"
},
"TITLES": {
"ALL_ARTICLES": "All Articles",
"MINE": "My Articles",
"DRAFT": "Draft Articles",
"ARCHIVED": "Archived Articles"
"ALL_ARTICLES": "Όλα Τα Άρθρα",
"MINE": "Τα Άρθρα Μου",
"DRAFT": "Πρόχειρα Άρθρα",
"ARCHIVED": "Αρχειοθετημένα Άρθρα"
}
},
"EDIT_HEADER": {
"ALL_ARTICLES": "All Articles",
"PUBLISH_BUTTON": "Publish",
"MOVE_TO_ARCHIVE_BUTTON": "Move to archived",
"PREVIEW": "Preview",
"ADD_TRANSLATION": "Add translation",
"OPEN_SIDEBAR": "Open sidebar",
"CLOSE_SIDEBAR": "Close sidebar",
"SAVING": "Saving...",
"SAVED": "Saved"
"ALL_ARTICLES": "Όλα Τα Άρθρα",
"PUBLISH_BUTTON": "Δημοσιευμένο",
"MOVE_TO_ARCHIVE_BUTTON": "Μετακίνηση στα αρχειοθετημένα",
"PREVIEW": "Προεπισκόπηση",
"ADD_TRANSLATION": "Προσθήκη μετάφρασης",
"OPEN_SIDEBAR": "Άνοιγμα πλευρικής μπάρας",
"CLOSE_SIDEBAR": "Κλείσιμο πλευρικής μπάρας",
"SAVING": "Αποθηκεύεται...",
"SAVED": "Αποθηκεύτηκε"
},
"ARTICLE_SETTINGS": {
"TITLE": "Article Settings",
"TITLE": "Ρυθμίσεις Άρθρου",
"FORM": {
"CATEGORY": {
"LABEL": "Κατηγορία",
"TITLE": "Select category",
"PLACEHOLDER": "Select category",
"NO_RESULT": "No category found",
"SEARCH_PLACEHOLDER": "Search category"
"TITLE": "Επιλογή κατηγορίας",
"PLACEHOLDER": "Επιλογή κατηγορίας",
"NO_RESULT": "Δεν βρέθηκε καμία κατηγορία",
"SEARCH_PLACEHOLDER": "Αναζήτηση κατηγορίας"
},
"AUTHOR": {
"LABEL": "Author",
"TITLE": "Select author",
"PLACEHOLDER": "Select author",
"NO_RESULT": "No authors found",
"SEARCH_PLACEHOLDER": "Search author"
"LABEL": "Συγγραφέας",
"TITLE": "Επιλογή συγγραφέα",
"PLACEHOLDER": "Επιλογή συγγραφέα",
"NO_RESULT": "Δεν βρέθηκαν συγγραφείς",
"SEARCH_PLACEHOLDER": "Αναζήτηση συγγραφέα"
},
"META_TITLE": {
"LABEL": "Meta title",
"PLACEHOLDER": "Add a meta title"
"LABEL": "Μετα-τίτλος",
"PLACEHOLDER": "Προσθήκη meta Τίτλου"
},
"META_DESCRIPTION": {
"LABEL": "Meta description",
"PLACEHOLDER": "Add your meta description for better SEO results..."
"LABEL": "Meta περιγραφή",
"PLACEHOLDER": "Προσθέστε τη meta περιγραφή σας για καλύτερα αποτελέσματα SEO..."
},
"META_TAGS": {
"LABEL": "Meta tags",
"PLACEHOLDER": "Add meta tags separated by comma..."
"PLACEHOLDER": "Προσθήκη μετα-ετικετών διαχωρισμένων με κόμμα..."
}
},
"BUTTONS": {
"ARCHIVE": "Archive article",
"DELETE": "Delete article"
"ARCHIVE": "Αρχειοθέτηση άρθρου",
"DELETE": "Διαγραφή άρθρου"
}
},
"PORTAL": {
"HEADER": "Portals",
"DEFAULT": "Default",
"NEW_BUTTON": "New Portal",
"HEADER": "Πύλες",
"DEFAULT": "Προεπιλογή",
"NEW_BUTTON": "Νέα Πύλη",
"ACTIVE_BADGE": "ενεργή",
"CHOOSE_LOCALE_LABEL": "Choose a locale",
"LOADING_MESSAGE": "Loading portals...",
"ARTICLES_LABEL": "articles",
"NO_PORTALS_MESSAGE": "There are no available portals",
"ADD_NEW_LOCALE": "Add a new locale",
"CHOOSE_LOCALE_LABEL": "Επιλέξτε μια γλώσσα",
"LOADING_MESSAGE": "Φόρτωση πυλών...",
"ARTICLES_LABEL": "άρθρα",
"NO_PORTALS_MESSAGE": "Δεν υπάρχουν διαθέσιμες πύλες",
"ADD_NEW_LOCALE": "Προσθέστε μια νέα γλώσσα",
"POPOVER": {
"TITLE": "Portals",
"PORTAL_SETTINGS": "Portal settings",
"SUBTITLE": "You have multiple portals and can have different locales for each portal.",
"TITLE": "Πύλες",
"PORTAL_SETTINGS": "Ρυθμίσεις πύλης",
"SUBTITLE": "Έχετε πολλαπλές πύλες με διαφορετικές γλώσσες για κάθε πύλη.",
"CANCEL_BUTTON_LABEL": "Άκυρο",
"CHOOSE_LOCALE_BUTTON": "Choose Locale"
"CHOOSE_LOCALE_BUTTON": "Επιλέξτε γλώσσα"
},
"PORTAL_SETTINGS": {
"LIST_ITEM": {
"HEADER": {
"COUNT_LABEL": "articles",
"ADD": "Add locale",
"VISIT": "Visit site",
"COUNT_LABEL": "άρθρα",
"ADD": "Προσθήκη γλώσσας",
"VISIT": "Επίσκεψη site",
"SETTINGS": "Ρυθμίσεις",
"DELETE": "Διαγραφή"
},
"PORTAL_CONFIG": {
"TITLE": "Portal Configurations",
"TITLE": "Ρυθμίσεις Πύλης",
"ITEMS": {
"NAME": "Όνομα",
"DOMAIN": "Custom domain",
"DOMAIN": "Προσαρμοσμένο Domain",
"SLUG": "Slug",
"TITLE": "Portal title",
"THEME": "Theme color",
"SUB_TEXT": "Portal sub text"
"TITLE": "Τίτλος πύλης",
"THEME": "Χρώμα θέματος",
"SUB_TEXT": "Υποκείμενο πύλης"
}
},
"AVAILABLE_LOCALES": {
"TITLE": "Available locales",
"TITLE": "Διαθέσιμες γλώσσες",
"TABLE": {
"NAME": "Locale name",
"CODE": "Locale code",
"ARTICLE_COUNT": "No. of articles",
"CATEGORIES": "No. of categories",
"SWAP": "Swap",
"NAME": "Όνομα γλώσσας",
"CODE": "Κωδικός γλώσσας",
"ARTICLE_COUNT": "Αριθμός άρθρων",
"CATEGORIES": "Αριθμός κατηγοριών",
"SWAP": "Εναλλαγή",
"DELETE": "Διαγραφή",
"DEFAULT_LOCALE": "Default"
"DEFAULT_LOCALE": "Προεπιλογή"
}
}
},
"DELETE_PORTAL": {
"TITLE": "Delete portal",
"MESSAGE": "Are you sure you want to delete this portal",
"YES": "Yes, delete portal",
"NO": "No, keep portal",
"TITLE": "Διαγραφή πύλης",
"MESSAGE": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν την πύλη",
"YES": "Ναι, διαγραφή πύλης",
"NO": "Όχι, διατήρηση πύλης",
"API": {
"DELETE_SUCCESS": "Portal deleted successfully",
"DELETE_ERROR": "Error while deleting portal"
"DELETE_SUCCESS": "Η πύλη διαγράφηκε επιτυχώς",
"DELETE_ERROR": "Σφάλμα κατά τη διαγραφή της πύλης"
}
}
},
"EDIT": {
"HEADER_TEXT": "Edit portal",
"HEADER_TEXT": "Επεξεργασία πύλης",
"TABS": {
"BASIC_SETTINGS": {
"TITLE": "Basic information"
"TITLE": "Βασικές πληροφορίες"
},
"CUSTOMIZATION_SETTINGS": {
"TITLE": "Portal customization"
"TITLE": "Προσαρμογή πύλης"
},
"CATEGORY_SETTINGS": {
"TITLE": "Categories"
"TITLE": "Κατηγορίες"
},
"LOCALE_SETTINGS": {
"TITLE": "Locales"
"TITLE": "Γλώσσες"
}
},
"CATEGORIES": {
"TITLE": "Categories in",
"NEW_CATEGORY": "New category",
"TITLE": "Κατηγορίες στο",
"NEW_CATEGORY": "Νέα κατηγορία",
"TABLE": {
"NAME": "Όνομα",
"DESCRIPTION": "Περιγραφή",
"LOCALE": "Locale",
"ARTICLE_COUNT": "No. of articles",
"LOCALE": "Γλώσσα",
"ARTICLE_COUNT": "Αριθμός άρθρων",
"ACTION_BUTTON": {
"EDIT": "Edit category",
"DELETE": "Delete category"
"EDIT": "Επεξεργασία κατηγορίας",
"DELETE": "Διαγραφή κατηγορίας"
},
"EMPTY_TEXT": "No categories found"
"EMPTY_TEXT": "Δεν βρέθηκαν κατηγορίες"
}
},
"EDIT_BASIC_INFO": {
"BUTTON_TEXT": "Update basic settings"
"BUTTON_TEXT": "Ενημέρωση βασικών ρυθμίσεων"
}
},
"ADD": {
"CREATE_FLOW": [
{
"title": "Help center information",
"title": "Πληροφορίες κέντρου βοήθειας",
"route": "new_portal_information",
"body": "Basic information about portal",
"CREATE_BASIC_SETTING_BUTTON": "Create portal basic settings"
"body": "Βασικές πληροφορίες σχετικά με την πύλη",
"CREATE_BASIC_SETTING_BUTTON": "Δημιουργία βασικών ρυθμίσεων πύλης"
},
{
"title": "Help center customization",
"title": "Προσαρμογή του κέντρου βοήθειας",
"route": "portal_customization",
"body": "Customize portal",
"UPDATE_PORTAL_BUTTON": "Update portal settings"
"body": "Προσαρμογή πύλης",
"UPDATE_PORTAL_BUTTON": "Ενημέρωση ρυθμίσεων πύλης"
},
{
"title": "Voila! 🎉",
"title": "Έξοχα! 🎉",
"route": "portal_finish",
"body": "You're all set!",
"body": "Είναι όλα έτοιμα!",
"FINISH": "Τέλος"
}
],
"CREATE_FLOW_PAGE": {
"BACK_BUTTON": "Πίσω",
"BASIC_SETTINGS_PAGE": {
"HEADER": "Create Portal",
"TITLE": "Help center information",
"CREATE_BASIC_SETTING_BUTTON": "Create portal basic settings"
"HEADER": "Δημιουργία Πύλης",
"TITLE": "Πληροφορίες κέντρου βοήθειας",
"CREATE_BASIC_SETTING_BUTTON": "Δημιουργία βασικών ρυθμίσεων πύλης"
},
"CUSTOMIZATION_PAGE": {
"HEADER": "Portal customisation",
"TITLE": "Help center customization",
"UPDATE_PORTAL_BUTTON": "Update portal settings"
"HEADER": "Προσαρμογή πύλης",
"TITLE": "Προσαρμογή του κέντρου βοήθειας",
"UPDATE_PORTAL_BUTTON": "Ενημέρωση ρυθμίσεων πύλης"
},
"FINISH_PAGE": {
"TITLE": "Voila!🎉 You're all set up!",
"MESSAGE": "You can now see this created portal on your all portals page.",
"FINISH": "Go to all portals page"
"TITLE": "Έξοχα!🎉 Έχετε ρυθμιστεί!",
"MESSAGE": "Τώρα μπορείτε να δείτε την πύλη που δημιουργήθηκε στη σελίδα με όλες τις πύλες.",
"FINISH": "Μετάβαση στις πύλες"
}
},
"LOGO": {
"LABEL": "Logo",
"UPLOAD_BUTTON": "Upload logo",
"HELP_TEXT": "This logo will be displayed on the portal header."
"LABEL": "Λογότυπο",
"UPLOAD_BUTTON": "Μεταφόρτωση λογότυπου",
"HELP_TEXT": "Το λογότυπο θα εμφανιστεί στην κεφαλίδα της πύλης."
},
"NAME": {
"LABEL": "Όνομα",
"PLACEHOLDER": "Portal name",
"HELP_TEXT": "The name will be used in the public facing portal internally.",
"PLACEHOLDER": "Όνομα πύλης",
"HELP_TEXT": "Το όνομα θα χρησιμοποιηθεί στο κοινό που βλέπει την πύλη εσωτερικά.",
"ERROR": "Απαιτείται όνομα"
},
"SLUG": {
"LABEL": "Slug",
"PLACEHOLDER": "Portal slug for urls",
"ERROR": "Slug is required"
"PLACEHOLDER": "Slug Πύλης για τα urls",
"ERROR": "Το Slug είναι απαραίτητο"
},
"DOMAIN": {
"LABEL": "Custom Domain",
"PLACEHOLDER": "Portal custom domain",
"HELP_TEXT": "Add only If you want to use a custom domain for your portals.",
"ERROR": "Custom Domain is required"
"LABEL": "Προσαρμοσμένο Domain",
"PLACEHOLDER": "Προσαρμοσμένος τομέας πύλης",
"HELP_TEXT": "Προσθήκη μόνο Αν θέλετε να χρησιμοποιήσετε ένα προσαρμοσμένο τομέα για τις πύλες σας. (Automatic Translation).",
"ERROR": "Ο Προσαρμοσμένος Τομέας απαιτείται"
},
"HOME_PAGE_LINK": {
"LABEL": "Home Page Link",
"PLACEHOLDER": "Portal home page link",
"HELP_TEXT": "The link used to return from the portal to the home page.",
"ERROR": "Home Page Link is required"
"LABEL": "Σύνδεσμος Αρχικής Σελίδας",
"PLACEHOLDER": "Σύνδεσμος αρχικής σελίδας πύλης",
"HELP_TEXT": "Ο σύνδεσμος που χρησιμοποιείται για την επιστροφή από την πύλη στην αρχική σελίδα.",
"ERROR": "Ο σύνδεσμος αρχικής σελίδας απαιτείται"
},
"THEME_COLOR": {
"LABEL": "Portal theme color",
"HELP_TEXT": "This color will show as the theme color for the portal."
"LABEL": "Χρώμα θέματος πύλης",
"HELP_TEXT": "Αυτό το χρώμα θα εμφανίζεται ως το χρώμα θέματος της πύλης. "
},
"PAGE_TITLE": {
"LABEL": "Page Title",
"PLACEHOLDER": "Portal page title",
"HELP_TEXT": "The page title will be used in the public facing portal.",
"ERROR": "Page title is required"
"LABEL": "Τίτλος Σελίδας",
"PLACEHOLDER": "Τίτλος σελίδας πύλης",
"HELP_TEXT": "Ο τίτλος της σελίδας θα χρησιμοποιηθεί στην πύλη που βλέπει το κοινό.",
"ERROR": "Ο τίτλος είναι απαραίτητος"
},
"HEADER_TEXT": {
"LABEL": "Header Text",
"PLACEHOLDER": "Portal header text",
"HELP_TEXT": "The Portal header text will be used in the public facing portal.",
"ERROR": "Portal header text is required"
"LABEL": "Κείμενο Κεφαλίδας",
"PLACEHOLDER": "Κείμενο κεφαλίδας πύλης",
"HELP_TEXT": "Το κείμενο κεφαλίδας πύλης θα χρησιμοποιηθεί στο κοινό που βλέπει πύλη.",
"ERROR": "Απαιτείται κείμενο κεφαλίδας πύλης"
},
"API": {
"SUCCESS_MESSAGE_FOR_BASIC": "Portal created successfully.",
"ERROR_MESSAGE_FOR_BASIC": "Couldn't create the portal. Try again.",
"SUCCESS_MESSAGE_FOR_UPDATE": "Portal updated successfully.",
"ERROR_MESSAGE_FOR_UPDATE": "Couldn't update the portal. Try again."
"SUCCESS_MESSAGE_FOR_BASIC": "Ο φάκελος δημιουργήθηκε με επιτυχία.",
"ERROR_MESSAGE_FOR_BASIC": "Δεν ήταν δυνατή η δημιουργία της πύλης. Δοκιμάστε ξανά.",
"SUCCESS_MESSAGE_FOR_UPDATE": "Η πύλη ενημερώθηκε με επιτυχία.",
"ERROR_MESSAGE_FOR_UPDATE": "Δεν ήταν δυνατή η ενημέρωση της πύλης. Δοκιμάστε ξανά."
}
},
"ADD_LOCALE": {
"TITLE": "Add a new locale",
"SUB_TITLE": "This adds a new locale to your available translation list.",
"PORTAL": "Portal",
"TITLE": "Προσθέστε μια νέα γλώσσα",
"SUB_TITLE": "Προσθέτει μια νέα γλώσσα στη διαθέσιμη λίστα μεταφράσεών σας.",
"PORTAL": "Πύλη",
"LOCALE": {
"LABEL": "Locale",
"PLACEHOLDER": "Choose a locale",
"ERROR": "Locale is required"
"LABEL": "Γλώσσα",
"PLACEHOLDER": "Επιλέξτε μια γλώσσα",
"ERROR": "Η γλώσσα απαιτείται"
},
"BUTTONS": {
"CREATE": "Create locale",
"CREATE": "Δημιουργία γλώσσας",
"CANCEL": "Άκυρο"
},
"API": {
"SUCCESS_MESSAGE": "Locale added successfully",
"ERROR_MESSAGE": "Unable to add locale. Try again."
"SUCCESS_MESSAGE": "Η γλώσσα προστέθηκε επιτυχώς",
"ERROR_MESSAGE": "Δεν είναι δυνατή η προσθήκη γλώσσας. Δοκιμάστε ξανά."
}
},
"CHANGE_DEFAULT_LOCALE": {
"API": {
"SUCCESS_MESSAGE": "Default locale updated successfully",
"ERROR_MESSAGE": "Unable to update default locale. Try again."
"SUCCESS_MESSAGE": "Η προεπιλεγμένη γλώσσα ενημερώθηκε επιτυχώς",
"ERROR_MESSAGE": "Δεν είναι δυνατή η ενημέρωση της προεπιλεγμένης γλώσσας. Δοκιμάστε ξανά."
}
},
"DELETE_LOCALE": {
"API": {
"SUCCESS_MESSAGE": "Locale removed from portal successfully",
"ERROR_MESSAGE": "Unable to remove locale from portal. Try again."
"SUCCESS_MESSAGE": "Η γλώσσα αφαιρέθηκε επιτυχώς από την πύλη",
"ERROR_MESSAGE": "Δεν είναι δυνατή η αφαίρεση γλώσσας από την πύλη. Δοκιμάστε ξανά."
}
}
},
"TABLE": {
"LOADING_MESSAGE": "Loading articles...",
"404": "No articles matches your search 🔍",
"NO_ARTICLES": "There are no available articles",
"LOADING_MESSAGE": "Φόρτωση άρθρων...",
"404": "Δεν υπάρχουν άρθρα που να ταιριάζουν στην αναζήτησή σας 🔍",
"NO_ARTICLES": "Δεν υπάρχουν διαθέσιμα άρθρα",
"HEADERS": {
"TITLE": "Τίτλος",
"CATEGORY": "Κατηγορία",
"READ_COUNT": "Read count",
"READ_COUNT": "Πλήθος ανάγνωσεων",
"STATUS": "Κατάσταση",
"LAST_EDITED": "Last edited"
"LAST_EDITED": "Τελευταία επεξεργασία"
},
"COLUMNS": {
"BY": "by"
"BY": "από"
}
},
"EDIT_ARTICLE": {
"LOADING": "Loading article...",
"TITLE_PLACEHOLDER": "Article title goes here",
"CONTENT_PLACEHOLDER": "Write your article here",
"LOADING": "Φόρτωση άρθρου...",
"TITLE_PLACEHOLDER": "Ο τίτλος του άρθρου εμφανίζεται εδώ",
"CONTENT_PLACEHOLDER": "Γράψτε το άρθρο σας εδώ",
"API": {
"ERROR": "Error while saving article"
"ERROR": "Σφάλμα κατά την αποθήκευση άρθρου"
}
},
"PUBLISH_ARTICLE": {
"API": {
"ERROR": "Error while publishing article",
"SUCCESS": "Article publishied successfully"
"ERROR": "Σφάλμα κατά τη δημοσίευση του άρθρου",
"SUCCESS": "Το Άρθρο δημοσιεύθηκε με επιτυχία"
}
},
"ARCHIVE_ARTICLE": {
"API": {
"ERROR": "Error while archiving article",
"SUCCESS": "Article archived successfully"
"ERROR": "Σφάλμα κατά την αρχειοθέτηση άρθρου",
"SUCCESS": "Το άρθρο αρχειοθετήθηκε επιτυχώς"
}
},
"DELETE_ARTICLE": {
"MODAL": {
"CONFIRM": {
"TITLE": "Επιβεβαίωση Διαγραφής",
"MESSAGE": "Are you sure to delete the article?",
"MESSAGE": "Είστε βέβαιοι να διαγράψετε το άρθρο;",
"YES": "Ναι, Διέγραψε το",
"NO": "Όχι, Κράτησε τον/την"
}
},
"API": {
"SUCCESS_MESSAGE": "Article deleted successfully",
"ERROR_MESSAGE": "Error while deleting article"
"SUCCESS_MESSAGE": "Η επαφή διαγράφηκε επιτυχώς",
"ERROR_MESSAGE": "Σφάλμα κατά τη διαγραφή άρθρου"
}
},
"CREATE_ARTICLE": {
"ERROR_MESSAGE": "Please add the article heading and content then only you can update the settings"
"ERROR_MESSAGE": "Παρακαλώ προσθέστε την επικεφαλίδα και το περιεχόμενο του άρθρου για να μπορείτε να ενημερώσετε τις ρυθμίσεις"
},
"SIDEBAR": {
"SEARCH": {
"PLACEHOLDER": "Search for articles"
"PLACEHOLDER": "Αναζήτηση άρθρων"
}
},
"CATEGORY": {
"ADD": {
"TITLE": "Create a category",
"SUB_TITLE": "The category will be used in the public facing portal to categorize articles.",
"PORTAL": "Portal",
"LOCALE": "Locale",
"TITLE": "Δημιουργία κατηγορίας",
"SUB_TITLE": "Η κατηγορία θα χρησιμοποιηθεί στην πύλη που βλέπει το κοινό για την κατηγοριοποίηση των άρθρων.",
"PORTAL": "Πύλη",
"LOCALE": "Γλώσσα",
"NAME": {
"LABEL": "Όνομα",
"PLACEHOLDER": "Category name",
"HELP_TEXT": "The category name will be used in the public facing portal to categorize articles.",
"PLACEHOLDER": "Όνομα κατηγορίας",
"HELP_TEXT": "Η κατηγορία θα χρησιμοποιηθεί στην πύλη που βλέπει το κοινό για την κατηγοριοποίηση των άρθρων.",
"ERROR": "Απαιτείται όνομα"
},
"SLUG": {
"LABEL": "Slug",
"PLACEHOLDER": "Category slug for urls",
"PLACEHOLDER": "Slug κατηγορίας για urls",
"HELP_TEXT": "app.chatwoot.com/hc/my-portal/en-US/categories/my-slug",
"ERROR": "Slug is required"
"ERROR": "Το Slug είναι απαραίτητο"
},
"DESCRIPTION": {
"LABEL": "Περιγραφή",
"PLACEHOLDER": "Give a short description about the category.",
"PLACEHOLDER": "Δώστε μια σύντομη περιγραφή της κατηγορίας.",
"ERROR": "Η περιγραφή απαιτείται"
},
"BUTTONS": {
"CREATE": "Create category",
"CREATE": "Δημιουργία κατηγορίας",
"CANCEL": "Άκυρο"
},
"API": {
"SUCCESS_MESSAGE": "Category created successfully",
"ERROR_MESSAGE": "Unable to create category"
"SUCCESS_MESSAGE": "Η κατηγορία δημιουργήθηκε με επιτυχία",
"ERROR_MESSAGE": "Αδυναμία δημιουργίας κατηγορίας"
}
},
"EDIT": {
"TITLE": "Edit a category",
"SUB_TITLE": "Editing a category will update the category in the public facing portal.",
"PORTAL": "Portal",
"LOCALE": "Locale",
"TITLE": "Επεξεργασία κατηγορίας",
"SUB_TITLE": "Η επεξεργασία μιας κατηγορίας θα ενημερώσει την κατηγορία στην πύλη που βλέπει το κοινό.",
"PORTAL": "Πύλη",
"LOCALE": "Γλώσσα",
"NAME": {
"LABEL": "Όνομα",
"PLACEHOLDER": "Category name",
"HELP_TEXT": "The category name will be used in the public facing portal to categorize articles.",
"PLACEHOLDER": "Όνομα κατηγορίας",
"HELP_TEXT": "Η κατηγορία θα χρησιμοποιηθεί στην πύλη που βλέπει το κοινό για την κατηγοριοποίηση των άρθρων.",
"ERROR": "Απαιτείται όνομα"
},
"SLUG": {
"LABEL": "Slug",
"PLACEHOLDER": "Category slug for urls",
"PLACEHOLDER": "Slug κατηγορίας για urls",
"HELP_TEXT": "app.chatwoot.com/hc/my-portal/en-US/categories/my-slug",
"ERROR": "Slug is required"
"ERROR": "Το Slug είναι απαραίτητο"
},
"DESCRIPTION": {
"LABEL": "Περιγραφή",
"PLACEHOLDER": "Give a short description about the category.",
"PLACEHOLDER": "Δώστε μια σύντομη περιγραφή της κατηγορίας.",
"ERROR": "Η περιγραφή απαιτείται"
},
"BUTTONS": {
"CREATE": "Update category",
"CREATE": "Επεξεργασία κατηγορίας",
"CANCEL": "Άκυρο"
},
"API": {
"SUCCESS_MESSAGE": "Category updated successfully",
"ERROR_MESSAGE": "Unable to update category"
"SUCCESS_MESSAGE": "Η ετικέτα ενημερώθηκε επιτυχώς",
"ERROR_MESSAGE": "Αδύνατη η ενημέρωση της κατηγορίας"
}
},
"DELETE": {
"API": {
"SUCCESS_MESSAGE": "Category deleted successfully",
"ERROR_MESSAGE": "Unable to delete category"
"SUCCESS_MESSAGE": "Η καμπάνια διαγράφηκε επιτυχώς",
"ERROR_MESSAGE": "Δεν είναι δυνατή η διαγραφή κατηγορίας"
}
}
}

View file

@ -112,10 +112,10 @@
"ERROR": "Το πεδίο είναι απαραίτητο"
},
"MESSAGING_SERVICE_SID": {
"LABEL": "Messaging Service SID",
"PLACEHOLDER": "Please enter your Twilio Messaging Service SID",
"LABEL": "SID Υπηρεσίας Μηνυμάτων",
"PLACEHOLDER": "Παρακαλώ εισάγετε το Twilio Messaging Service SID σας",
"ERROR": "Το πεδίο είναι απαραίτητο",
"USE_MESSAGING_SERVICE": "Use a Twilio Messaging Service"
"USE_MESSAGING_SERVICE": "Χρήση μιας υπηρεσίας μηνυμάτων Twilio"
},
"CHANNEL_TYPE": {
"LABEL": "Τύπος Καναλιού",
@ -239,7 +239,9 @@
},
"API_CALLBACK": {
"TITLE": "URL επανάκλησης",
"SUBTITLE": "Θα πρέπει να ρυθμίσετε το URL webhook στο facebook developer portal με το URL που αναφέρεται εδώ."
"SUBTITLE": "Πρέπει να ρυθμίσετε τη διεύθυνση URL του webhook και το διακριτικό επαλήθευσης στην πύλη Προγραμματιστή Facebook με τις τιμές που εμφανίζονται παρακάτω.",
"WEBHOOK_URL": "Σύνδεσμος Webhook",
"WEBHOOK_VERIFICATION_TOKEN": "Token Επαλήθευσης Webhook"
},
"SUBMIT_BUTTON": "Δημιουργία Καναλιού WhatsApp",
"API": {
@ -357,7 +359,7 @@
},
"FINISH": {
"TITLE": "Το κιβώτιο σας είναι έτοιμο!",
"MESSAGE": "Μπορείτε να συνομιλείτε με τους πελάτες σας από το νέο κανάλι. Καλή υποστήριξη ",
"MESSAGE": "Μπορείτε να συνομιλείτε με τους πελάτες σας από το νέο κανάλι. Καλή υποστήριξη",
"BUTTON_TEXT": "Μετάβαση",
"MORE_SETTINGS": "Περισσότερες ρυθμίσεις",
"WEBSITE_SUCCESS": "Επιτυχής δημιουργία του καναλιού ιστοσελίδας. Αντιγράψτε τον κώδικα που παρουσιάζεται παρακάτω, και τοποθετήστε τον στην ιστοσελίδα σας. Την επόμενη φορά που κάποιος πελάτης χρησιμοποιήσει το 'live chat', η συνομιλία θα εμφανιστεί στο κιβώτιο εισερχομένων σας."
@ -414,7 +416,7 @@
"CAMPAIGN": "Καμπάνιες",
"PRE_CHAT_FORM": "Φόρμα Προ-Συνομιλίας",
"BUSINESS_HOURS": "Ώρες Εργασίας",
"WIDGET_BUILDER": "Widget Builder"
"WIDGET_BUILDER": "Δημιουργός Widget"
},
"SETTINGS": "Ρυθμίσεις",
"FEATURES": {
@ -579,10 +581,10 @@
"WIDGET_BUILDER": {
"WIDGET_OPTIONS": {
"AVATAR": {
"LABEL": "Website Avatar",
"LABEL": "Avatar Ιστοσελίδας",
"DELETE": {
"API": {
"SUCCESS_MESSAGE": "Avatar deleted successfully",
"SUCCESS_MESSAGE": "Το Avatar διαγράφηκε επιτυχώς",
"ERROR_MESSAGE": "Υπήρξε ένα σφάλμα, παρακαλώ προσπαθήστε ξανά"
}
}
@ -590,7 +592,7 @@
"WEBSITE_NAME": {
"LABEL": "Όνομα Ιστοσελίδας",
"PLACE_HOLDER": "Συμπληρώστε την ονομασία της ιστοσελίδας σας (π.χ: Ελληνικό Μεσογειακό Πανεπιστήμιο)",
"ERROR": "Please enter a valid website name"
"ERROR": "Παρακαλώ δώστε ένα έγκυρο όνομα ιστοσελίδας"
},
"WELCOME_HEADING": {
"LABEL": "Καλώς ήλθατε (Heading)",
@ -601,42 +603,42 @@
"PLACE_HOLDER": "Είναι απλό να συνδεθείτε μαζί μας. Ζητήστε μας οτιδήποτε, ή μοιραστείτε την εμπειρία σας."
},
"REPLY_TIME": {
"LABEL": "Reply Time",
"LABEL": "Χρόνος Απάντησης",
"IN_A_FEW_MINUTES": "Σε μερικά λεπτά",
"IN_A_FEW_HOURS": "Σε μερικές ώρες",
"IN_A_DAY": "Σε μία ημέρα"
},
"WIDGET_COLOR_LABEL": "Χρώμα Widget",
"WIDGET_BUBBLE_POSITION_LABEL": "Widget Bubble Position",
"WIDGET_BUBBLE_TYPE_LABEL": "Widget Bubble Type",
"WIDGET_BUBBLE_POSITION_LABEL": "Θέση Φυσαλίδας Widget",
"WIDGET_BUBBLE_TYPE_LABEL": "Τύπος Φυσαλίδας Widget",
"WIDGET_BUBBLE_LAUNCHER_TITLE": {
"DEFAULT": "Συνομιλήστε μαζί μας",
"LABEL": "Widget Bubble Launcher Title",
"LABEL": "Τίτλος Εκκίνησης Φυσαλίδας Widget",
"PLACE_HOLDER": "Συνομιλήστε μαζί μας"
},
"UPDATE": {
"BUTTON_TEXT": "Update Widget Settings",
"BUTTON_TEXT": "Ενημέρωση Ρυθμίσεων Widget",
"API": {
"SUCCESS_MESSAGE": "Widget settings updated successfully",
"ERROR_MESSAGE": "Unable to update widget settings"
"SUCCESS_MESSAGE": "Οι ρυθμίσεις widget ενημερώθηκαν με επιτυχία",
"ERROR_MESSAGE": "Δεν είναι δυνατή η ενημέρωση ρυθμίσεων widget"
}
},
"WIDGET_VIEW_OPTION": {
"PREVIEW": "Preview",
"PREVIEW": "Προεπισκόπηση",
"SCRIPT": "Script"
},
"WIDGET_BUBBLE_POSITION": {
"LEFT": "Left",
"RIGHT": "Right"
"LEFT": "Αριστερά",
"RIGHT": "Δεξιά"
},
"WIDGET_BUBBLE_TYPE": {
"STANDARD": "Standard",
"EXPANDED_BUBBLE": "Expanded Bubble"
"EXPANDED_BUBBLE": "Εκτεταμένη Φυσαλίδα"
}
},
"WIDGET_SCREEN": {
"DEFAULT": "Default",
"CHAT": "Chat"
"DEFAULT": "Προεπιλογή",
"CHAT": "Συνομιλία"
},
"REPLY_TIME": {
"IN_A_FEW_MINUTES": "Τυπικά έχετε απάντηση σε μερικά λεπτά",
@ -649,11 +651,11 @@
},
"BODY": {
"TEAM_AVAILABILITY": {
"ONLINE": "We are Online",
"ONLINE": "Είμαστε online",
"OFFLINE": "Προς το παρόν, είμαστε εκτός"
},
"USER_MESSAGE": "Hi",
"AGENT_MESSAGE": "Hello"
"USER_MESSAGE": "Γειά",
"AGENT_MESSAGE": "Γειά σας"
},
"BRANDING_TEXT": "με την δύναμη του Chatwoot",
"SCRIPT_SETTINGS": "\n window.chatwootSettings = {options};"

View file

@ -86,49 +86,49 @@
"BUTTON_TEXT": "Σύνδεση"
},
"DASHBOARD_APPS": {
"TITLE": "Dashboard Apps",
"HEADER_BTN_TXT": "Add a new dashboard app",
"SIDEBAR_TXT": "<p><b>Dashboard Apps</b></p><p>Dashboard Apps allow organizations to embed an application inside the Chatwoot dashboard to provide the context for customer support agents. This feature allows you to create an application independently and embed that inside the dashboard to provide user information, their orders, or their previous payment history.</p><p>When you embed your application using the dashboard in Chatwoot, your application will get the context of the conversation and contact as a window event. Implement a listener for the message event on your page to receive the context.</p><p>To add a new dashboard app, click on the button 'Add a new dashboard app'.</p>",
"DESCRIPTION": "Dashboard Apps allow organizations to embed an application inside the dashboard to provide the context for customer support agents. This feature allows you to create an application independently and embed that to provide user information, their orders, or their previous payment history.",
"TITLE": "Εφαρμογές Dashboard",
"HEADER_BTN_TXT": "Προσθήκη νέας εφαρμογής Dashboard",
"SIDEBAR_TXT": "<p><b>Εφαρμογές Dashboard</b></p><p>Οι εφαρμογές Dashboard επιτρέπουν σε οργανισμούς να ενσωματώσουν μια εφαρμογή μέσα στο ταμπλό Chatwoot για να παρέχουν το πλαίσιο για τους πράκτορες υποστήριξης πελατών. Αυτό το χαρακτηριστικό σας επιτρέπει να δημιουργήσετε μια εφαρμογή ανεξάρτητα και ενσωματωμένη που μέσα στον πίνακα ελέγχου για να παρέχει πληροφορίες χρήστη, τις παραγγελίες τους, ή το ιστορικό προηγούμενων πληρωμών τους.</p><p>Όταν ενσωματώσετε την εφαρμογή σας χρησιμοποιώντας το Dashboard στο Chatwoot, η εφαρμογή σας θα πάρει το πλαίσιο της συνομιλίας και θα επικοινωνήσει ως ένα παράθυρο εκδήλωσης. Εφαρμόστε έναν ακροατή για το γεγονός του μηνύματος στη σελίδα σας για να λάβετε το πλαίσιο.</p><p>Για να προσθέσετε μια νέα εφαρμογή ταμπλό, κάντε κλικ στο κουμπί 'Προσθήκη μιας νέας εφαρμογής Dashboard'.</p>",
"DESCRIPTION": "Οι εφαρμογές Dashboard επιτρέπουν στους οργανισμούς να ενσωματώσουν μια εφαρμογή μέσα στον πίνακα ελέγχου για να παρέχουν το περιεχόμενο για τους πράκτορες υποστήριξης πελατών. Αυτή η λειτουργία σας επιτρέπει να δημιουργήσετε μια εφαρμογή ανεξάρτητα και ενσωματωμένη που θα παρέχει πληροφορίες χρήστη, τις παραγγελίες τους, ή το ιστορικό προηγούμενων πληρωμών τους.",
"LIST": {
"404": "There are no dashboard apps configured on this account yet",
"LOADING": "Fetching dashboard apps...",
"404": "Δεν έχουν δημιουργηθεί εφαρμογές Dashboard για αυτόν το λογαριασμό",
"LOADING": "Λήψη εφαρμογών dashboard ...",
"TABLE_HEADER": [
"Όνομα",
"Endpoint"
],
"EDIT_TOOLTIP": "Edit app",
"DELETE_TOOLTIP": "Delete app"
"EDIT_TOOLTIP": "Επεξεργασία εφαρμογής",
"DELETE_TOOLTIP": "Διαγραφή εφαρμογής"
},
"FORM": {
"TITLE_LABEL": "Όνομα",
"TITLE_PLACEHOLDER": "Enter a name for your dashboard app",
"TITLE_ERROR": "A name for the dashboard app is required",
"TITLE_PLACEHOLDER": "Εισάγετε όνομα για την εφαρμογή dashboard",
"TITLE_ERROR": "Απαιτείται ένα όνομα για την εφαρμογή dashboard",
"URL_LABEL": "Endpoint",
"URL_PLACEHOLDER": "Enter the endpoint URL where your app is hosted",
"URL_ERROR": "A valid URL is required"
"URL_PLACEHOLDER": "Εισάγετε το URL του endpoint όπου φιλοξενείται η εφαρμογή σας",
"URL_ERROR": "Απαιτείται ένα έγκυρο URL"
},
"CREATE": {
"HEADER": "Add a new dashboard app",
"HEADER": "Προσθήκη νέας εφαρμογής Dashboard",
"FORM_SUBMIT": "Καταχώρηση",
"FORM_CANCEL": "Άκυρο",
"API_SUCCESS": "Dashboard app configured successfully",
"API_ERROR": "We couldn't create an app. Please try again later"
"API_SUCCESS": "Η εφαρμογή dashboard ρυθμίστηκε επιτυχώς",
"API_ERROR": "Δεν μπορούμε να δημιουργήσουμε μια εφαρμογή. Παρακαλώ δοκιμάστε ξανά αργότερα"
},
"UPDATE": {
"HEADER": "Edit dashboard app",
"HEADER": "Επεξεργασία εφαρμογής dashboard",
"FORM_SUBMIT": "Ενημέρωση",
"FORM_CANCEL": "Άκυρο",
"API_SUCCESS": "Dashboard app updated successfully",
"API_ERROR": "We couldn't update the app. Please try again later"
"API_SUCCESS": "Η εφαρμογή dashboard ενημερώθηκε με επιτυχία",
"API_ERROR": "Δεν ήταν δυνατή η ενημέρωση της εφαρμογής. Παρακαλώ δοκιμάστε ξανά αργότερα"
},
"DELETE": {
"CONFIRM_YES": "Yes, delete it",
"CONFIRM_NO": "No, keep it",
"CONFIRM_YES": "Ναι, Διέγραψε την",
"CONFIRM_NO": "Όχι, Κράτησε την",
"TITLE": "Επιβεβαίωση Διαγραφής",
"MESSAGE": "Are you sure to delete the app - %{appName}?",
"API_SUCCESS": "Dashboard app deleted successfully",
"API_ERROR": "We couldn't delete the app. Please try again later"
"MESSAGE": "Είστε βέβαιοι να διαγράψετε την εφαρμογή - %{appName};",
"API_SUCCESS": "Η εφαρμογή dashboard διαγράφηκε επιτυχώς",
"API_ERROR": "Δεν μπορούμε να διαγράψουμε την εφαρμογή. Παρακαλώ δοκιμάστε ξανά αργότερα"
}
}
}

View file

@ -0,0 +1,5 @@
{
"MACROS": {
"HEADER": "Μακροεντολές"
}
}

View file

@ -19,6 +19,21 @@
"TITLE": "Προφίλ",
"NOTE": "Η διεύθυνση email είναι η ταυτότητά σας και χρησιμοποιείται για την είσοδο (login) σας."
},
"SEND_MESSAGE": {
"TITLE": "Πλήκτρο συντόμευσης για αποστολή μηνυμάτων",
"NOTE": "Μπορείτε να επιλέξετε μια συντόμευση (είτε εισάγετε είτε Cmd/Ctrl+Enter) με βάση την προτίμηση για το γράψιμο.",
"UPDATE_SUCCESS": "Οι ρυθμίσεις σας έχουν ενημερωθεί με επιτυχία",
"CARD": {
"ENTER_KEY": {
"HEADING": "Enter (↵)",
"CONTENT": "Αποστολή μηνυμάτων πατώντας το πλήκτρο Enter αντί να πατήσετε το κουμπί αποστολής."
},
"CMD_ENTER_KEY": {
"HEADING": "Cmd/Ctrl + Enter (⌘ + ↵)",
"CONTENT": "Αποστολή μηνυμάτων πατώντας το πλήκτρο Enter αντί να πατήσετε το κουμπί αποστολής."
}
}
},
"MESSAGE_SIGNATURE_SECTION": {
"TITLE": "Προσωπική υπογραφή μηνύματος",
"NOTE": "Δημιουργήστε μια προσωπική υπογραφή, η οποία θα προστεθεί σε όλα τα μηνύματα που στέλνετε από τα εισερχόμενα email σας. Χρησιμοποιήστε τον επεξεργαστή πλούσιου περιεχομένου για να δημιουργήσετε μια εξατομικευμένη υπογραφή.",
@ -126,8 +141,8 @@
"TRAIL_BUTTON": "Αγόρασε τώρα",
"DELETED_USER": "Διαγραμμένος Χρήστης",
"ACCOUNT_SUSPENDED": {
"TITLE": "Account Suspended",
"MESSAGE": "Your account is suspended. Please reach out to the support team for more information."
"TITLE": "Αναστολή Λογαριασμού",
"MESSAGE": "Ο λογαριασμός σας έχει ανασταλεί. Επικοινωνήστε με την ομάδα υποστήριξης για περισσότερες πληροφορίες."
}
},
"COMPONENTS": {
@ -164,6 +179,7 @@
"CONTACTS": "Επαφές",
"HOME": "Αρχική",
"AGENTS": "Πράκτορες",
"AGENT_BOTS": "Bots",
"INBOXES": "Κιβώτια Εισερχομένων",
"NOTIFICATIONS": "Ειδοποιήσεις",
"CANNED_RESPONSES": "Έτοιμες Απαντήσεις",
@ -174,8 +190,9 @@
"LABELS": "Ετικέτες",
"CUSTOM_ATTRIBUTES": "Προσαρμοζόμενες Ιδιότητες",
"AUTOMATION": "Αυτοματισμός",
"MACROS": "Μακροεντολές",
"TEAMS": "Ομάδες",
"BILLING": "Billing",
"BILLING": "Χρεώσεις",
"CUSTOM_VIEWS_FOLDER": "Φάκελοι",
"CUSTOM_VIEWS_SEGMENTS": "Τμήματα",
"ALL_CONTACTS": "Όλες Οι Επαφές",
@ -197,33 +214,33 @@
"REPORTS_OVERVIEW": "Επισκόπηση",
"FACEBOOK_REAUTHORIZE": "Η σύνδεση Facebook έχει λήξει, παρακαλώ ξανασυνδεθείτε στο Facebook για να συνεχίσετε",
"HELP_CENTER": {
"TITLE": "Help Center (Beta)",
"ALL_ARTICLES": "All Articles",
"MY_ARTICLES": "My Articles",
"DRAFT": "Draft",
"ARCHIVED": "Archived",
"TITLE": "Κέντρο Βοήθειας (Beta)",
"ALL_ARTICLES": "Όλα Τα Άρθρα",
"MY_ARTICLES": "Τα Άρθρα Μου",
"DRAFT": "Πρόχειρο",
"ARCHIVED": "Αρχειοθετημένο",
"CATEGORY": "Κατηγορία",
"CATEGORY_EMPTY_MESSAGE": "No categories found"
"CATEGORY_EMPTY_MESSAGE": "Δεν βρέθηκαν κατηγορίες"
},
"DOCS": "Read docs"
"DOCS": "Ανάγνωση εγγράφων"
},
"BILLING_SETTINGS": {
"TITLE": "Billing",
"TITLE": "Χρεώσεις",
"CURRENT_PLAN": {
"TITLE": "Current Plan",
"PLAN_NOTE": "You are currently subscribed to the **%{plan}** plan with **%{quantity}** licenses"
"TITLE": "Τρέχον Πλάνο",
"PLAN_NOTE": "Αυτή τη στιγμή έχετε εγγραφεί στο πλάνο **%{plan}** με **%{quantity}** άδειες"
},
"MANAGE_SUBSCRIPTION": {
"TITLE": "Manage your subscription",
"DESCRIPTION": "View your previous invoices, edit your billing details, or cancel your subscription.",
"BUTTON_TXT": "Go to the billing portal"
"TITLE": "Διαχειριστείτε τη συνδρομή σας",
"DESCRIPTION": "Δείτε τα προηγούμενα τιμολόγια σας, επεξεργαστείτε τα στοιχεία χρέωσης ή ακυρώστε τη συνδρομή σας.",
"BUTTON_TXT": "Μετάβαση στην πύλη χρέωσης"
},
"CHAT_WITH_US": {
"TITLE": "Need help?",
"DESCRIPTION": "Do you face any issues in billing? We are here to help.",
"TITLE": "Χρειάζεστε βοήθεια;",
"DESCRIPTION": "Αντιμετωπίζετε οποιαδήποτε προβλήματα στην τιμολόγηση? Είμαστε εδώ για να βοηθήσουμε.",
"BUTTON_TXT": "Συνομιλήστε μαζί μας"
},
"NO_BILLING_USER": "Your billing account is being configured. Please refresh the page and try again."
"NO_BILLING_USER": "Ο λογαριασμός χρέωσης έχει ρυθμιστεί. Παρακαλώ ανανεώστε τη σελίδα και προσπαθήστε ξανά."
},
"CREATE_ACCOUNT": {
"NO_ACCOUNT_WARNING": "Ωχ! Δεν μπορέσαμε να βρούμε κανένα λογαριασμό Chatwoot. Παρακαλούμε δημιουργήστε ένα νέο λογαριασμό για να συνεχίσετε.",

View file

@ -0,0 +1,5 @@
{
"AGENT_BOTS": {
"HEADER": "Bots"
}
}

View file

@ -2,9 +2,11 @@
"BULK_ACTION": {
"CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected",
"AGENT_SELECT_LABEL": "Select Agent",
"ASSIGN_CONFIRMATION_LABEL": "Are you sure you want to assign %{conversationCount} %{conversationLabel} to",
"ASSIGN_CONFIRMATION_LABEL": "Are you sure to assign %{conversationCount} %{conversationLabel} to",
"UNASSIGN_CONFIRMATION_LABEL": "Are you sure to unassign %{conversationCount} %{conversationLabel}?",
"GO_BACK_LABEL": "Go back",
"ASSIGN_LABEL": "Assign",
"YES": "Yes",
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
"ASSIGN_FAILED": "Failed to assign conversations, please try again",

View file

@ -111,7 +111,6 @@
"TIP_AUDIORECORDER_ICON": "Record audio",
"TIP_AUDIORECORDER_PERMISSION": "Allow access to audio",
"TIP_AUDIORECORDER_ERROR": "Could not open the audio",
"ENTER_TO_SEND": "Enter to send",
"DRAG_DROP": "Drag and drop here to attach",
"START_AUDIO_RECORDING": "Start audio recording",
"STOP_AUDIO_RECORDING": "Stop audio recording",
@ -151,7 +150,8 @@
},
"CONTEXT_MENU": {
"COPY": "Copy",
"DELETE": "Delete"
"DELETE": "Delete",
"CREATE_A_CANNED_RESPONSE": "Add to canned responses"
}
},
"EMAIL_TRANSCRIPT": {

View file

@ -307,7 +307,7 @@
"PUBLISH_ARTICLE": {
"API": {
"ERROR": "Error while publishing article",
"SUCCESS": "Article publishied successfully"
"SUCCESS": "Article published successfully"
}
},
"ARCHIVE_ARTICLE": {

View file

@ -239,7 +239,9 @@
},
"API_CALLBACK": {
"TITLE": "Callback URL",
"SUBTITLE": "You have to configure the webhook URL in facebook developer portal with the URL mentioned here."
"SUBTITLE": "You have to configure the webhook URL and the verification token in the Facebook Developer portal with the values shown below.",
"WEBHOOK_URL": "Webhook URL",
"WEBHOOK_VERIFICATION_TOKEN": "Webhook Verification Token"
},
"SUBMIT_BUTTON": "Create WhatsApp Channel",
"API": {
@ -357,7 +359,7 @@
},
"FINISH": {
"TITLE": "Your Inbox is ready!",
"MESSAGE": "You can now engage with your customers through your new Channel. Happy supporting ",
"MESSAGE": "You can now engage with your customers through your new Channel. Happy supporting",
"BUTTON_TEXT": "Take me there",
"MORE_SETTINGS": "More settings",
"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."

View file

@ -1,55 +1,59 @@
import { default as _advancedFilters } from './advancedFilters.json';
import { default as _agentMgmt } from './agentMgmt.json';
import { default as _attributesMgmt } from './attributesMgmt.json';
import { default as _automation } from './automation.json';
import { default as _bulkActions } from './bulkActions.json';
import { default as _campaign } from './campaign.json';
import { default as _cannedMgmt } from './cannedMgmt.json';
import { default as _chatlist } from './chatlist.json';
import { default as _contact } from './contact.json';
import { default as _contactFilters } from './contactFilters.json';
import { default as _conversation } from './conversation.json';
import { default as _csatMgmtMgmt } from './csatMgmt.json';
import { default as _generalSettings } from './generalSettings.json';
import { default as _inboxMgmt } from './inboxMgmt.json';
import { default as _integrationApps } from './integrationApps.json';
import { default as _integrations } from './integrations.json';
import { default as _labelsMgmt } from './labelsMgmt.json';
import { default as _login } from './login.json';
import { default as _report } from './report.json';
import { default as _resetPassword } from './resetPassword.json';
import { default as _setNewPassword } from './setNewPassword.json';
import { default as _settings } from './settings.json';
import { default as _signup } from './signup.json';
import { default as _teamsSettings } from './teamsSettings.json';
import { default as _whatsappTemplates } from './whatsappTemplates.json';
import { default as _helpCenter } from './helpCenter.json';
import advancedFilters from './advancedFilters.json';
import agentBots from './agentBots.json';
import agentMgmt from './agentMgmt.json';
import attributesMgmt from './attributesMgmt.json';
import automation from './automation.json';
import bulkActions from './bulkActions.json';
import campaign from './campaign.json';
import cannedMgmt from './cannedMgmt.json';
import chatlist from './chatlist.json';
import contact from './contact.json';
import contactFilters from './contactFilters.json';
import conversation from './conversation.json';
import csatMgmtMgmt from './csatMgmt.json';
import generalSettings from './generalSettings.json';
import helpCenter from './helpCenter.json';
import inboxMgmt from './inboxMgmt.json';
import integrationApps from './integrationApps.json';
import integrations from './integrations.json';
import labelsMgmt from './labelsMgmt.json';
import login from './login.json';
import macros from './macros.json';
import report from './report.json';
import resetPassword from './resetPassword.json';
import setNewPassword from './setNewPassword.json';
import settings from './settings.json';
import signup from './signup.json';
import teamsSettings from './teamsSettings.json';
import whatsappTemplates from './whatsappTemplates.json';
export default {
..._advancedFilters,
..._agentMgmt,
..._attributesMgmt,
..._automation,
..._campaign,
..._cannedMgmt,
..._chatlist,
..._contact,
..._contactFilters,
..._conversation,
..._csatMgmtMgmt,
..._generalSettings,
..._inboxMgmt,
..._integrationApps,
..._integrations,
..._labelsMgmt,
..._login,
..._report,
..._resetPassword,
..._setNewPassword,
..._settings,
..._signup,
..._teamsSettings,
..._whatsappTemplates,
..._bulkActions,
..._helpCenter,
...advancedFilters,
...agentBots,
...agentMgmt,
...attributesMgmt,
...automation,
...bulkActions,
...campaign,
...cannedMgmt,
...chatlist,
...contact,
...contactFilters,
...conversation,
...csatMgmtMgmt,
...generalSettings,
...helpCenter,
...inboxMgmt,
...integrationApps,
...integrations,
...labelsMgmt,
...login,
...macros,
...report,
...resetPassword,
...setNewPassword,
...settings,
...signup,
...teamsSettings,
...whatsappTemplates,
};

View file

@ -0,0 +1,5 @@
{
"MACROS": {
"HEADER": "Macros"
}
}

View file

@ -19,6 +19,21 @@
"TITLE": "Profile",
"NOTE": "Your email address is your identity and is used to log in."
},
"SEND_MESSAGE": {
"TITLE": "Hotkey to send messages",
"NOTE": "You can select a hotkey (either Enter or Cmd/Ctrl+Enter) based on your preference of writing.",
"UPDATE_SUCCESS": "Your settings have been updated successfully",
"CARD": {
"ENTER_KEY": {
"HEADING": "Enter (↵)",
"CONTENT": "Send messages by pressing Enter key instead of clicking the send button."
},
"CMD_ENTER_KEY": {
"HEADING": "Cmd/Ctrl + Enter (⌘ + ↵)",
"CONTENT": "Send messages by pressing Cmd/Ctrl + enter key instead of clicking the send button."
}
}
},
"MESSAGE_SIGNATURE_SECTION": {
"TITLE": "Personal message signature",
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
@ -84,7 +99,11 @@
},
"AVAILABILITY": {
"LABEL": "Availability",
"STATUSES_LIST": ["Online", "Busy", "Offline"]
"STATUSES_LIST": [
"Online",
"Busy",
"Offline"
]
},
"EMAIL": {
"LABEL": "Your email address",
@ -161,6 +180,7 @@
"CONTACTS": "Contacts",
"HOME": "Home",
"AGENTS": "Agents",
"AGENT_BOTS": "Bots",
"INBOXES": "Inboxes",
"NOTIFICATIONS": "Notifications",
"CANNED_RESPONSES": "Canned Responses",
@ -171,6 +191,7 @@
"LABELS": "Labels",
"CUSTOM_ATTRIBUTES": "Custom Attributes",
"AUTOMATION": "Automation",
"MACROS": "Macros",
"TEAMS": "Teams",
"BILLING": "Billing",
"CUSTOM_VIEWS_FOLDER": "Folders",
@ -215,7 +236,6 @@
"DESCRIPTION": "View your previous invoices, edit your billing details, or cancel your subscription.",
"BUTTON_TXT": "Go to the billing portal"
},
"CHAT_WITH_US": {
"TITLE": "Need help?",
"DESCRIPTION": "Do you face any issues in billing? We are here to help.",

View file

@ -111,7 +111,6 @@
"TIP_AUDIORECORDER_ICON": "Grabar audio",
"TIP_AUDIORECORDER_PERMISSION": "Permitir el acceso a audio",
"TIP_AUDIORECORDER_ERROR": "No se pudo abrir el audio",
"ENTER_TO_SEND": "Ingresar para enviar",
"DRAG_DROP": "Arrastra y suelta aquí para adjuntar",
"START_AUDIO_RECORDING": "Iniciar grabación de audio",
"STOP_AUDIO_RECORDING": "Detener grabación de audio",
@ -151,7 +150,8 @@
},
"CONTEXT_MENU": {
"COPY": "Copiar",
"DELETE": "Eliminar"
"DELETE": "Eliminar",
"CREATE_A_CANNED_RESPONSE": "Añadir a respuestas predefinidas"
}
},
"EMAIL_TRANSCRIPT": {

View file

@ -19,6 +19,21 @@
"TITLE": "Perfil",
"NOTE": "Su dirección de correo electrónico es su identidad y se utiliza para iniciar sesión."
},
"SEND_MESSAGE": {
"TITLE": "Hotkey to send messages",
"NOTE": "You can select a hotkey (either Enter or Cmd/Ctrl+Enter) based on your preference of writing.",
"UPDATE_SUCCESS": "Your settings have been updated successfully",
"CARD": {
"ENTER_KEY": {
"HEADING": "Enter (↵)",
"CONTENT": "Send messages by pressing Enter key instead of clicking the send button."
},
"CMD_ENTER_KEY": {
"HEADING": "Cmd/Ctrl + Enter (⌘ + ↵)",
"CONTENT": "Send messages by pressing Cmd/Ctrl + enter key instead of clicking the send button."
}
}
},
"MESSAGE_SIGNATURE_SECTION": {
"TITLE": "Firma de mensaje personal",
"NOTE": "Cree una firma de mensaje personal que se añadirá a todos los mensajes que envíe desde su buzón de correo electrónico. Utilice el rico editor de contenidos para crear una firma altamente personalizada.",

View file

@ -111,7 +111,6 @@
"TIP_AUDIORECORDER_ICON": "ضبط صدا",
"TIP_AUDIORECORDER_PERMISSION": "اجازه دسترسی به صدا",
"TIP_AUDIORECORDER_ERROR": "صدا را نمی‌توان باز کند",
"ENTER_TO_SEND": "برای ارسال Enter را بزنید",
"DRAG_DROP": "برای ضمیمه کردن درگ و درآپ کنید",
"START_AUDIO_RECORDING": "در حال شروع ضبط صدا",
"STOP_AUDIO_RECORDING": "در حال توقف ضبط صدا",
@ -151,7 +150,8 @@
},
"CONTEXT_MENU": {
"COPY": "کپی",
"DELETE": "حذف"
"DELETE": "حذف",
"CREATE_A_CANNED_RESPONSE": "اضافه کردن به پاسخ‌های آماده"
}
},
"EMAIL_TRANSCRIPT": {

View file

@ -19,6 +19,21 @@
"TITLE": "پروفایل",
"NOTE": "آدرس ایمیل شما هویت شماست و برای ورود به سیستم استفاده می شود."
},
"SEND_MESSAGE": {
"TITLE": "کلید میانبر برای ارسال پیام",
"NOTE": "شما می‌توانید یک کلید میانبر (Enter یا Cmd/Ctrl+Enter) را بر اساس راحت بودن در نوشتن انتخاب کنید.",
"UPDATE_SUCCESS": "تنظیمات شما با موفقیت به‌روزرسانی شد",
"CARD": {
"ENTER_KEY": {
"HEADING": "Enter (↵)",
"CONTENT": "به جای کلیک بر روی دکمه ارسال، با فشردن کلید Enter پیام ارسال کنید."
},
"CMD_ENTER_KEY": {
"HEADING": "Cmd/Ctrl + Enter (⌘ + ↵)",
"CONTENT": "به جای کلیک کردن بر روی دکمه ارسال، با فشردن کلید Cmd/Ctrl + enter پیام ارسال کنید."
}
}
},
"MESSAGE_SIGNATURE_SECTION": {
"TITLE": "امضای پیام شخصی",
"NOTE": "یک امضای پیام شخصی ایجاد کنید که به همه پیام‌هایی که از صندوق ورودی ایمیل ارسال می‌کنید اضافه شود. از ویرایشگر محتوای غنی برای ایجاد یک امضای بسیار شخصی استفاده کنید.",

View file

@ -111,7 +111,6 @@
"TIP_AUDIORECORDER_ICON": "Record audio",
"TIP_AUDIORECORDER_PERMISSION": "Allow access to audio",
"TIP_AUDIORECORDER_ERROR": "Could not open the audio",
"ENTER_TO_SEND": "Enter to send",
"DRAG_DROP": "Drag and drop here to attach",
"START_AUDIO_RECORDING": "Start audio recording",
"STOP_AUDIO_RECORDING": "Stop audio recording",
@ -151,7 +150,8 @@
},
"CONTEXT_MENU": {
"COPY": "Kopioi",
"DELETE": "Poista"
"DELETE": "Poista",
"CREATE_A_CANNED_RESPONSE": "Add to canned responses"
}
},
"EMAIL_TRANSCRIPT": {

View file

@ -19,6 +19,21 @@
"TITLE": "Profiili",
"NOTE": "Sähköpostiosoitteesi on identiteettisi ja sitä käytetään sisäänkirjautumiseen."
},
"SEND_MESSAGE": {
"TITLE": "Hotkey to send messages",
"NOTE": "You can select a hotkey (either Enter or Cmd/Ctrl+Enter) based on your preference of writing.",
"UPDATE_SUCCESS": "Your settings have been updated successfully",
"CARD": {
"ENTER_KEY": {
"HEADING": "Enter (↵)",
"CONTENT": "Send messages by pressing Enter key instead of clicking the send button."
},
"CMD_ENTER_KEY": {
"HEADING": "Cmd/Ctrl + Enter (⌘ + ↵)",
"CONTENT": "Send messages by pressing Cmd/Ctrl + enter key instead of clicking the send button."
}
}
},
"MESSAGE_SIGNATURE_SECTION": {
"TITLE": "Personal message signature",
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",

View file

@ -111,7 +111,6 @@
"TIP_AUDIORECORDER_ICON": "Enregistrer l'audio",
"TIP_AUDIORECORDER_PERMISSION": "Autoriser l'accès à l'audio",
"TIP_AUDIORECORDER_ERROR": "Impossible d'ouvrir l'audio",
"ENTER_TO_SEND": "Entrer pour envoyer",
"DRAG_DROP": "Glissez et déposez ici pour lier",
"START_AUDIO_RECORDING": "Démarrer l'enregistrement audio",
"STOP_AUDIO_RECORDING": "Arrêter l'enregistrement audio",
@ -151,7 +150,8 @@
},
"CONTEXT_MENU": {
"COPY": "Copier",
"DELETE": "Supprimer"
"DELETE": "Supprimer",
"CREATE_A_CANNED_RESPONSE": "Add to canned responses"
}
},
"EMAIL_TRANSCRIPT": {

View file

@ -19,6 +19,21 @@
"TITLE": "Profil",
"NOTE": "Votre adresse de courriel est votre identité et est utilisée pour vous connecter."
},
"SEND_MESSAGE": {
"TITLE": "Hotkey to send messages",
"NOTE": "You can select a hotkey (either Enter or Cmd/Ctrl+Enter) based on your preference of writing.",
"UPDATE_SUCCESS": "Your settings have been updated successfully",
"CARD": {
"ENTER_KEY": {
"HEADING": "Enter (↵)",
"CONTENT": "Send messages by pressing Enter key instead of clicking the send button."
},
"CMD_ENTER_KEY": {
"HEADING": "Cmd/Ctrl + Enter (⌘ + ↵)",
"CONTENT": "Send messages by pressing Cmd/Ctrl + enter key instead of clicking the send button."
}
}
},
"MESSAGE_SIGNATURE_SECTION": {
"TITLE": "Signature du message personnel",
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",

View file

@ -54,12 +54,12 @@
"RECEIVED_VIA_EMAIL": "התקבל בדואר אלקטרוני",
"VIEW_TWEET_IN_TWITTER": "צפה בציוץ בטוויטר",
"REPLY_TO_TWEET": "הגב לציוץ זה",
"LINK_TO_STORY": "Go to instagram story",
"LINK_TO_STORY": "מעבר לסטורי באינסטגרם",
"SENT": "נשלח בהצלחה",
"NO_MESSAGES": "אין הודעות",
"NO_CONTENT": "אין תוכן זמין",
"HIDE_QUOTED_TEXT": "הסתר טקסט מצוטט",
"SHOW_QUOTED_TEXT": "הצג טקסט מצוטט",
"MESSAGE_READ": "Read"
"MESSAGE_READ": "קרא"
}
}

View file

@ -3,11 +3,11 @@
"NOT_AVAILABLE": "לא זמין",
"EMAIL_ADDRESS": "כתובת מייל",
"PHONE_NUMBER": "מספר טלפון",
"IDENTIFIER": "Identifier",
"IDENTIFIER": "מזהה",
"COPY_SUCCESSFUL": "הועתק ללוח בהצלחה",
"COMPANY": "חברה",
"LOCATION": "מיקום",
"BROWSER_LANGUAGE": "Browser Language",
"BROWSER_LANGUAGE": "שפת דפדפן",
"CONVERSATION_TITLE": "פרטי שיחה",
"VIEW_PROFILE": "צפה בפרופיל",
"BROWSER": "דפדפן",
@ -75,8 +75,8 @@
"DELETE_NOTE": {
"CONFIRM": {
"TITLE": "אשר מחיקה",
"MESSAGE": "Are you want sure to delete this note?",
"YES": "Yes, Delete it",
"MESSAGE": "אתה בטוח שתרצה למחוק את ההערה הזו?",
"YES": "כן, מחק",
"NO": "לא, השאר"
}
},

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