Compare commits

..
Author SHA1 Message Date
Vishnu Narayanan 0ef3b3b5b5 fix: force build_contact and increment_view_count methods to use writing role
These methods are used by the widget to create a contact and helpcenter to
increment the view count. Since these methods are triggered by GET
request, Rails will use the reading role which will fail if the table
is readonly.
2024-05-24 17:31:35 +05:30
Vishnu NarayananandGitHub af084af774 Merge branch 'develop' into feat/add_read_replica_support 2024-05-16 08:41:13 +05:30
Vishnu Narayanan 5a682135f2 feat: configure auto db switching 2024-05-07 16:37:41 +10:00
Vishnu NarayananandGitHub a72dba1769 Merge branch 'develop' into feat/add_read_replica_support 2024-05-07 12:33:32 +10:00
Vishnu NarayananandGitHub cc653e072a Merge branch 'develop' into feat/add_read_replica_support 2024-05-03 14:39:38 +10:00
Vishnu Narayanan 7e64671a27 chore: add replica settings for test&dev 2024-05-03 14:37:37 +10:00
Vishnu Narayanan 43bca252f9 chore: revert db fallback initializer 2024-05-03 13:36:37 +10:00
Vishnu Narayanan 5dc74ff40f chore: debug gh action 2024-05-03 12:34:12 +10:00
Vishnu Narayanan 8dd735f3ea chore: debug gh action 2024-05-03 12:29:33 +10:00
Vishnu Narayanan ce69614662 Merge branch 'feat/add_read_replica_support' of github.com:chatwoot/chatwoot into feat/add_read_replica_support 2024-05-03 12:26:13 +10:00
Vishnu Narayanan 8a6b13fd85 chore: debug gh action 2024-05-03 12:25:47 +10:00
Vishnu NarayananandGitHub 2692dba5bd Merge branch 'develop' into feat/add_read_replica_support 2024-05-03 11:54:54 +10:00
Vishnu Narayanan 0095254e65 feat: handle if read_replica is not available 2024-05-02 18:59:42 +10:00
Vishnu Narayanan 3b34be5740 feat: add read replica support 2024-05-02 18:46:46 +10:00
389 changed files with 1195 additions and 3739 deletions
+7
View File
@@ -57,6 +57,13 @@ POSTGRES_PASSWORD=
RAILS_ENV=development
# Changes the Postgres query timeout limit. The default is 14 seconds. Modify only when required.
# POSTGRES_STATEMENT_TIMEOUT=14s
# read replica configuration for production
# POSTGRES_HOST_RR=postgres_rr
# create a new user with readonly permissions
# POSTGRES_USERNAME_RR=postgres_readonly
# POSTGRES_PASSWORD_RR=
RAILS_MAX_THREADS=5
# The email from which all outgoing emails are sent
-1
View File
@@ -45,7 +45,6 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
+1 -1
View File
@@ -77,7 +77,7 @@ gem 'jwt'
gem 'pundit'
# super admin
gem 'administrate', '>= 0.20.1'
gem 'administrate-field-active_storage', '>= 1.0.3'
gem 'administrate-field-active_storage', '>= 1.0.2'
gem 'administrate-field-belongs_to_search', '>= 0.9.0'
##--- gems for pubsub service ---##
+57 -59
View File
@@ -33,70 +33,70 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (7.0.8.3)
actionpack (= 7.0.8.3)
activesupport (= 7.0.8.3)
actioncable (7.0.8.1)
actionpack (= 7.0.8.1)
activesupport (= 7.0.8.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.8.3)
actionpack (= 7.0.8.3)
activejob (= 7.0.8.3)
activerecord (= 7.0.8.3)
activestorage (= 7.0.8.3)
activesupport (= 7.0.8.3)
actionmailbox (7.0.8.1)
actionpack (= 7.0.8.1)
activejob (= 7.0.8.1)
activerecord (= 7.0.8.1)
activestorage (= 7.0.8.1)
activesupport (= 7.0.8.1)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.8.3)
actionpack (= 7.0.8.3)
actionview (= 7.0.8.3)
activejob (= 7.0.8.3)
activesupport (= 7.0.8.3)
actionmailer (7.0.8.1)
actionpack (= 7.0.8.1)
actionview (= 7.0.8.1)
activejob (= 7.0.8.1)
activesupport (= 7.0.8.1)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.8.3)
actionview (= 7.0.8.3)
activesupport (= 7.0.8.3)
actionpack (7.0.8.1)
actionview (= 7.0.8.1)
activesupport (= 7.0.8.1)
rack (~> 2.0, >= 2.2.4)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (7.0.8.3)
actionpack (= 7.0.8.3)
activerecord (= 7.0.8.3)
activestorage (= 7.0.8.3)
activesupport (= 7.0.8.3)
actiontext (7.0.8.1)
actionpack (= 7.0.8.1)
activerecord (= 7.0.8.1)
activestorage (= 7.0.8.1)
activesupport (= 7.0.8.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.8.3)
activesupport (= 7.0.8.3)
actionview (7.0.8.1)
activesupport (= 7.0.8.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
active_record_query_trace (1.8)
activejob (7.0.8.3)
activesupport (= 7.0.8.3)
activejob (7.0.8.1)
activesupport (= 7.0.8.1)
globalid (>= 0.3.6)
activemodel (7.0.8.3)
activesupport (= 7.0.8.3)
activerecord (7.0.8.3)
activemodel (= 7.0.8.3)
activesupport (= 7.0.8.3)
activemodel (7.0.8.1)
activesupport (= 7.0.8.1)
activerecord (7.0.8.1)
activemodel (= 7.0.8.1)
activesupport (= 7.0.8.1)
activerecord-import (1.4.1)
activerecord (>= 4.2)
activestorage (7.0.8.3)
actionpack (= 7.0.8.3)
activejob (= 7.0.8.3)
activerecord (= 7.0.8.3)
activesupport (= 7.0.8.3)
activestorage (7.0.8.1)
actionpack (= 7.0.8.1)
activejob (= 7.0.8.1)
activerecord (= 7.0.8.1)
activesupport (= 7.0.8.1)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.8.3)
activesupport (7.0.8.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@@ -113,7 +113,7 @@ GEM
kaminari (~> 1.2.2)
sassc-rails (~> 2.1)
selectize-rails (~> 0.6)
administrate-field-active_storage (1.0.3)
administrate-field-active_storage (1.0.2)
administrate (>= 0.2.2)
rails (>= 7.0)
administrate-field-belongs_to_search (0.9.0)
@@ -461,7 +461,7 @@ GEM
mini_magick (4.12.0)
mini_mime (1.1.5)
mini_portile2 (2.8.6)
minitest (5.23.0)
minitest (5.22.3)
mock_redis (0.36.0)
ruby2_keywords
msgpack (1.7.0)
@@ -569,20 +569,20 @@ GEM
rack-test (2.1.0)
rack (>= 1.3)
rack-timeout (0.6.3)
rails (7.0.8.3)
actioncable (= 7.0.8.3)
actionmailbox (= 7.0.8.3)
actionmailer (= 7.0.8.3)
actionpack (= 7.0.8.3)
actiontext (= 7.0.8.3)
actionview (= 7.0.8.3)
activejob (= 7.0.8.3)
activemodel (= 7.0.8.3)
activerecord (= 7.0.8.3)
activestorage (= 7.0.8.3)
activesupport (= 7.0.8.3)
rails (7.0.8.1)
actioncable (= 7.0.8.1)
actionmailbox (= 7.0.8.1)
actionmailer (= 7.0.8.1)
actionpack (= 7.0.8.1)
actiontext (= 7.0.8.1)
actionview (= 7.0.8.1)
activejob (= 7.0.8.1)
activemodel (= 7.0.8.1)
activerecord (= 7.0.8.1)
activestorage (= 7.0.8.1)
activesupport (= 7.0.8.1)
bundler (>= 1.15.0)
railties (= 7.0.8.3)
railties (= 7.0.8.1)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
@@ -590,9 +590,9 @@ GEM
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
nokogiri (~> 1.14)
railties (7.0.8.3)
actionpack (= 7.0.8.3)
activesupport (= 7.0.8.3)
railties (7.0.8.1)
actionpack (= 7.0.8.1)
activesupport (= 7.0.8.1)
method_source
rake (>= 12.2)
thor (~> 1.0)
@@ -628,8 +628,7 @@ GEM
retriable (3.1.2)
reverse_markdown (2.1.1)
nokogiri
rexml (3.2.8)
strscan (>= 3.0.9)
rexml (3.2.5)
rspec-core (3.12.2)
rspec-support (~> 3.12.0)
rspec-expectations (3.12.3)
@@ -759,7 +758,6 @@ GEM
stackprof (0.2.25)
statsd-ruby (1.5.0)
stripe (8.5.0)
strscan (3.1.0)
telephone_number (1.4.20)
test-prof (1.2.1)
thor (1.3.1)
@@ -839,7 +837,7 @@ DEPENDENCIES
activerecord-import
acts-as-taggable-on
administrate (>= 0.20.1)
administrate-field-active_storage (>= 1.0.3)
administrate-field-active_storage (>= 1.0.2)
administrate-field-belongs_to_search (>= 0.9.0)
annotate
attr_extras
@@ -1,30 +0,0 @@
class V2::Reports::Conversations::BaseReportBuilder
pattr_initialize :account, :params
private
AVG_METRICS = %w[avg_first_response_time avg_resolution_time reply_time].freeze
COUNT_METRICS = %w[
conversations_count
incoming_messages_count
outgoing_messages_count
resolutions_count
bot_resolutions_count
bot_handoffs_count
].freeze
def builder_class(metric)
case metric
when *AVG_METRICS
V2::Reports::Timeseries::AverageReportBuilder
when *COUNT_METRICS
V2::Reports::Timeseries::CountReportBuilder
end
end
def log_invalid_metric
Rails.logger.error "ReportBuilder: Invalid metric - #{params[:metric]}"
{}
end
end
@@ -1,30 +0,0 @@
class V2::Reports::Conversations::MetricBuilder < V2::Reports::Conversations::BaseReportBuilder
def summary
{
conversations_count: count('conversations_count'),
incoming_messages_count: count('incoming_messages_count'),
outgoing_messages_count: count('outgoing_messages_count'),
avg_first_response_time: count('avg_first_response_time'),
avg_resolution_time: count('avg_resolution_time'),
resolutions_count: count('resolutions_count'),
reply_time: count('reply_time')
}
end
def bot_summary
{
bot_resolutions_count: count('bot_resolutions_count'),
bot_handoffs_count: count('bot_handoffs_count')
}
end
private
def count(metric)
builder_class(metric).new(account, builder_params(metric)).aggregate_value
end
def builder_params(metric)
params.merge({ metric: metric })
end
end
@@ -1,21 +0,0 @@
class V2::Reports::Conversations::ReportBuilder < V2::Reports::Conversations::BaseReportBuilder
def timeseries
perform_action(:timeseries)
end
def aggregate_value
perform_action(:aggregate_value)
end
private
def perform_action(method_name)
return builder.new(account, params).public_send(method_name) if builder.present?
log_invalid_metric
end
def builder
builder_class(params[:metric])
end
end
@@ -1,48 +0,0 @@
class V2::Reports::Timeseries::AverageReportBuilder < V2::Reports::Timeseries::BaseTimeseriesBuilder
def timeseries
grouped_average_time = reporting_events.average(average_value_key)
grouped_event_count = reporting_events.count
grouped_average_time.each_with_object([]) do |element, arr|
event_date, average_time = element
arr << {
value: average_time,
timestamp: event_date.in_time_zone(timezone).to_i,
count: grouped_event_count[event_date]
}
end
end
def aggregate_value
object_scope.average(average_value_key)
end
private
def event_name
metric_to_event_name = {
avg_first_response_time: :first_response,
avg_resolution_time: :conversation_resolved,
reply_time: :reply_time
}
metric_to_event_name[params[:metric].to_sym]
end
def object_scope
scope.reporting_events.where(name: event_name, created_at: range)
end
def reporting_events
@grouped_values = object_scope.group_by_period(
group_by,
:created_at,
default_value: 0,
range: range,
permit: %w[day week month year hour],
time_zone: timezone
)
end
def average_value_key
@average_value_key ||= params[:business_hours].present? ? :value_in_business_hours : :value
end
end
@@ -1,46 +0,0 @@
class V2::Reports::Timeseries::BaseTimeseriesBuilder
include TimezoneHelper
include DateRangeHelper
DEFAULT_GROUP_BY = 'day'.freeze
pattr_initialize :account, :params
def scope
case params[:type].to_sym
when :account
account
when :inbox
inbox
when :agent
user
when :label
label
when :team
team
end
end
def inbox
@inbox ||= account.inboxes.find(params[:id])
end
def user
@user ||= account.users.find(params[:id])
end
def label
@label ||= account.labels.find(params[:id])
end
def team
@team ||= account.teams.find(params[:id])
end
def group_by
@group_by ||= %w[day week month year hour].include?(params[:group_by]) ? params[:group_by] : DEFAULT_GROUP_BY
end
def timezone
@timezone ||= timezone_name_from_offset(params[:timezone_offset])
end
end
@@ -1,71 +0,0 @@
class V2::Reports::Timeseries::CountReportBuilder < V2::Reports::Timeseries::BaseTimeseriesBuilder
def timeseries
grouped_count.each_with_object([]) do |element, arr|
event_date, event_count = element
# The `event_date` is in Date format (without time), such as "Wed, 15 May 2024".
# We need a timestamp for the start of the day. However, we can't use `event_date.to_time.to_i`
# because it converts the date to 12:00 AM server timezone.
# The desired output should be 12:00 AM in the specified timezone.
arr << { value: event_count, timestamp: event_date.in_time_zone(timezone).to_i }
end
end
def aggregate_value
object_scope.count
end
private
def metric
@metric ||= params[:metric]
end
def object_scope
send("scope_for_#{metric}")
end
def scope_for_conversations_count
scope.conversations.where(account_id: account.id, created_at: range)
end
def scope_for_incoming_messages_count
scope.messages.where(account_id: account.id, created_at: range).incoming.unscope(:order)
end
def scope_for_outgoing_messages_count
scope.messages.where(account_id: account.id, created_at: range).outgoing.unscope(:order)
end
def scope_for_resolutions_count
scope.reporting_events.joins(:conversation).select(:conversation_id).where(
name: :conversation_resolved,
conversations: { status: :resolved }, created_at: range
).distinct
end
def scope_for_bot_resolutions_count
scope.reporting_events.joins(:conversation).select(:conversation_id).where(
name: :conversation_bot_resolved,
conversations: { status: :resolved }, created_at: range
).distinct
end
def scope_for_bot_handoffs_count
scope.reporting_events.joins(:conversation).select(:conversation_id).where(
name: :conversation_bot_handoff,
created_at: range
).distinct
end
def grouped_count
@grouped_values = object_scope.group_by_period(
group_by,
:created_at,
default_value: 0,
range: range,
permit: %w[day week month year hour],
time_zone: timezone
).count
end
end
@@ -1,93 +0,0 @@
class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::BaseController
before_action :fetch_conversation, only: [:link_issue, :linked_issues]
def teams
teams = linear_processor_service.teams
if teams[:error]
render json: { error: teams[:error] }, status: :unprocessable_entity
else
render json: teams[:data], status: :ok
end
end
def team_entities
team_id = permitted_params[:team_id]
team_entities = linear_processor_service.team_entities(team_id)
if team_entities[:error]
render json: { error: team_entities[:error] }, status: :unprocessable_entity
else
render json: team_entities[:data], status: :ok
end
end
def create_issue
issue = linear_processor_service.create_issue(permitted_params)
if issue[:error]
render json: { error: issue[:error] }, status: :unprocessable_entity
else
render json: issue[:data], status: :ok
end
end
def link_issue
issue_id = permitted_params[:issue_id]
title = permitted_params[:title]
issue = linear_processor_service.link_issue(conversation_link, issue_id, title)
if issue[:error]
render json: { error: issue[:error] }, status: :unprocessable_entity
else
render json: issue[:data], status: :ok
end
end
def unlink_issue
link_id = permitted_params[:link_id]
issue = linear_processor_service.unlink_issue(link_id)
if issue[:error]
render json: { error: issue[:error] }, status: :unprocessable_entity
else
render json: issue[:data], status: :ok
end
end
def linked_issues
issues = linear_processor_service.linked_issues(conversation_link)
if issues[:error]
render json: { error: issues[:error] }, status: :unprocessable_entity
else
render json: issues[:data], status: :ok
end
end
def search_issue
render json: { error: 'Specify search string with parameter q' }, status: :unprocessable_entity if params[:q].blank? && return
term = params[:q]
issues = linear_processor_service.search_issue(term)
if issues[:error]
render json: { error: issues[:error] }, status: :unprocessable_entity
else
render json: issues[:data], status: :ok
end
end
private
def conversation_link
"#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/#{Current.account.id}/conversations/#{@conversation.display_id}"
end
def fetch_conversation
@conversation = Current.account.conversations.find_by!(display_id: permitted_params[:conversation_id])
end
def linear_processor_service
Integrations::Linear::ProcessorService.new(account: Current.account)
end
def permitted_params
params.permit(:team_id, :conversation_id, :issue_id, :link_id, :title, :description, :assignee_id, :priority, label_ids: [])
end
end
@@ -5,17 +5,19 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
before_action :check_authorization
def index
builder = V2::Reports::Conversations::ReportBuilder.new(Current.account, report_params)
data = builder.timeseries
builder = V2::ReportBuilder.new(Current.account, report_params)
data = builder.build
render json: data
end
def summary
render json: build_summary(:summary)
render json: summary_metrics
end
def bot_summary
render json: build_summary(:bot_summary)
summary = V2::ReportBuilder.new(Current.account, current_summary_params).bot_summary
summary[:previous] = V2::ReportBuilder.new(Current.account, previous_summary_params).bot_summary
render json: summary
end
def agents
@@ -124,11 +126,10 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
}
end
def build_summary(method)
builder = V2::Reports::Conversations::MetricBuilder
current_summary = builder.new(Current.account, current_summary_params).send(method)
previous_summary = builder.new(Current.account, previous_summary_params).send(method)
current_summary.merge(previous: previous_summary)
def summary_metrics
summary = V2::ReportBuilder.new(Current.account, current_summary_params).summary
summary[:previous] = V2::ReportBuilder.new(Current.account, previous_summary_params).summary
summary
end
def conversation_metrics
@@ -2,10 +2,7 @@ module MicrosoftConcern
extend ActiveSupport::Concern
def microsoft_client
app_id = GlobalConfigService.load('AZURE_APP_ID', nil)
app_secret = GlobalConfigService.load('AZURE_APP_SECRET', nil)
::OAuth2::Client.new(app_id, app_secret,
::OAuth2::Client.new(ENV.fetch('AZURE_APP_ID', nil), ENV.fetch('AZURE_APP_SECRET', nil),
{
site: 'https://login.microsoftonline.com',
authorize_url: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
+1 -1
View File
@@ -58,7 +58,7 @@ class DashboardController < ActionController::Base
FB_APP_ID: GlobalConfigService.load('FB_APP_ID', ''),
FACEBOOK_API_VERSION: GlobalConfigService.load('FACEBOOK_API_VERSION', 'v17.0'),
IS_ENTERPRISE: ChatwootApp.enterprise?,
AZURE_APP_ID: GlobalConfigService.load('AZURE_APP_ID', ''),
AZURE_APP_ID: ENV.fetch('AZURE_APP_ID', ''),
GIT_SHA: GIT_HASH
}
end
+1 -1
View File
@@ -12,6 +12,6 @@ class MicrosoftController < ApplicationController
end
def microsoft_indentity
@identity_json = GlobalConfigService.load('AZURE_APP_ID', nil)
@identity_json = ENV.fetch('AZURE_APP_ID', nil)
end
end
@@ -35,8 +35,6 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController
@allowed_configs = case @config
when 'facebook'
%w[FB_APP_ID FB_VERIFY_TOKEN FB_APP_SECRET IG_VERIFY_TOKEN FACEBOOK_API_VERSION ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT]
when 'microsoft'
%w[AZURE_APP_ID AZURE_APP_SECRET]
when 'email'
['MAILER_INBOUND_EMAIL_DOMAIN']
else
+4 -2
View File
@@ -47,8 +47,10 @@ class WidgetsController < ActionController::Base
def build_contact
return if @contact.present?
@contact_inbox, @token = build_contact_inbox_with_token(@web_widget, additional_attributes)
@contact = @contact_inbox.contact
ActiveRecord::Base.connected_to(role: :writing) do
@contact_inbox, @token = build_contact_inbox_with_token(@web_widget, additional_attributes)
@contact = @contact_inbox.contact
end
end
def ensure_account_is_active
-19
View File
@@ -1,19 +0,0 @@
module TimezoneHelper
# ActiveSupport TimeZone is not aware of the current time, so ActiveSupport::Timezone[offset]
# would return the timezone without considering day light savings. To get the correct timezone,
# this method uses zone.now.utc_offset for comparison as referenced in the issues below
#
# https://github.com/rails/rails/pull/22243
# https://github.com/rails/rails/issues/21501
# https://github.com/rails/rails/issues/7297
def timezone_name_from_offset(offset)
return 'UTC' if offset.blank?
offset_in_seconds = offset.to_f * 3600
matching_zone = ActiveSupport::TimeZone.all.find do |zone|
zone.now.utc_offset == offset_in_seconds
end
return matching_zone.name if matching_zone
end
end
@@ -7,17 +7,79 @@
]"
>
<slot />
<chat-list-header
:page-title="pageTitle"
:has-applied-filters="hasAppliedFilters"
:has-active-folders="hasActiveFolders"
:active-status="activeStatus"
@add-folders="onClickOpenAddFoldersModal"
@delete-folders="onClickOpenDeleteFoldersModal"
@filters-modal="onToggleAdvanceFiltersModal"
@reset-filters="resetAndFetchData"
@basic-filter-change="onBasicFilterChange"
/>
<div
class="flex items-center justify-between px-4 py-0"
:class="{
'pb-3 border-b border-slate-75 dark:border-slate-700':
hasAppliedFiltersOrActiveFolders,
}"
>
<div class="flex max-w-[85%] justify-center items-center">
<h1
class="text-xl font-medium break-words truncate text-black-900 dark:text-slate-100"
:title="pageTitle"
>
{{ pageTitle }}
</h1>
<span
v-if="!hasAppliedFiltersOrActiveFolders"
class="p-1 my-0.5 mx-1 rounded-md capitalize bg-slate-50 dark:bg-slate-800 text-xxs text-slate-600 dark:text-slate-300"
>
{{ $t(`CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.${activeStatus}.TEXT`) }}
</span>
</div>
<div class="flex items-center gap-1">
<div v-if="hasAppliedFilters && !hasActiveFolders">
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.ADD.SAVE_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="secondary"
icon="save"
@click="onClickOpenAddFoldersModal"
/>
<woot-button
v-tooltip.top-end="$t('FILTER.CLEAR_BUTTON_LABEL')"
size="tiny"
variant="smooth"
color-scheme="alert"
icon="dismiss-circle"
@click="resetAndFetchData"
/>
</div>
<div v-if="hasActiveFolders">
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.EDIT.EDIT_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="secondary"
icon="edit"
@click="onToggleAdvanceFiltersModal"
/>
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.DELETE.DELETE_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="alert"
icon="delete"
@click="onClickOpenDeleteFoldersModal"
/>
</div>
<woot-button
v-else
v-tooltip.right="$t('FILTER.TOOLTIP_LABEL')"
variant="smooth"
color-scheme="secondary"
icon="filter"
size="tiny"
@click="onToggleAdvanceFiltersModal"
/>
<conversation-basic-filter
v-if="!hasAppliedFiltersOrActiveFolders"
@changeFilter="onBasicFilterChange"
/>
</div>
</div>
<add-custom-views
v-if="showAddFoldersModal"
@@ -111,15 +173,6 @@
@updateFolder="onUpdateSavedFilter"
/>
</woot-modal>
<woot-modal
:show.sync="showCustomSnoozeModal"
:on-close="hideCustomSnoozeModal"
>
<custom-snooze-modal
@close="hideCustomSnoozeModal"
@choose-time="chooseSnoozeTime"
/>
</woot-modal>
</div>
</template>
@@ -127,8 +180,8 @@
import { mapGetters } from 'vuex';
import VirtualList from 'vue-virtual-scroll-list';
import ChatListHeader from './ChatListHeader.vue';
import ConversationAdvancedFilter from './widgets/conversation/ConversationAdvancedFilter.vue';
import ConversationBasicFilter from './widgets/conversation/ConversationBasicFilter.vue';
import ChatTypeTabs from './widgets/ChatTypeTabs.vue';
import ConversationItem from './ConversationItem.vue';
import timeMixin from '../mixins/time';
@@ -152,15 +205,10 @@ import {
isOnUnattendedView,
} from '../store/modules/conversations/helpers/actionHelpers';
import { CONVERSATION_EVENTS } from '../helper/AnalyticsHelper/events';
import { CMD_SNOOZE_CONVERSATION } from 'dashboard/routes/dashboard/commands/commandBarBusEvents';
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
import { getUnixTime } from 'date-fns';
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';
import IntersectionObserver from './IntersectionObserver.vue';
export default {
components: {
ChatListHeader,
AddCustomViews,
ChatTypeTabs,
// eslint-disable-next-line vue/no-unused-components
@@ -168,9 +216,9 @@ export default {
ConversationAdvancedFilter,
DeleteCustomViews,
ConversationBulkActions,
ConversationBasicFilter,
IntersectionObserver,
VirtualList,
CustomSnoozeModal,
},
mixins: [
timeMixin,
@@ -247,7 +295,6 @@ export default {
root: this.$refs.conversationList,
rootMargin: '100px 0px 100px 0px',
},
showCustomSnoozeModal: false,
itemComponent: ConversationItem,
// virtualListExtraProps is to pass the props to the conversationItem component.
@@ -282,13 +329,12 @@ export default {
campaigns: 'campaigns/getAllCampaigns',
labels: 'labels/getLabels',
selectedConversations: 'bulkActions/getSelectedConversationIds',
contextMenuChatId: 'getContextMenuChatId',
}),
hasAppliedFilters() {
return this.appliedFilters.length !== 0;
},
hasActiveFolders() {
return Boolean(this.activeFolder && this.foldersId !== 0);
return this.activeFolder && this.foldersId !== 0;
},
hasAppliedFiltersOrActiveFolders() {
return this.hasAppliedFilters || this.hasActiveFolders;
@@ -512,11 +558,6 @@ export default {
bus.$on('fetch_conversation_stats', () => {
this.$store.dispatch('conversationStats/get', this.conversationFilters);
});
bus.$on(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
},
beforeDestroy() {
bus.$off(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
},
methods: {
updateVirtualListProps(key, value) {
@@ -987,49 +1028,12 @@ export default {
allSelectedConversationsStatus(status) {
if (!this.selectedConversations.length) return false;
return this.selectedConversations.every(item => {
return this.$store.getters.getConversationById(item)?.status === status;
return this.$store.getters.getConversationById(item).status === status;
});
},
onContextMenuToggle(state) {
this.isContextMenuOpen = state;
},
onCmdSnoozeConversation(snoozeType) {
if (snoozeType === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) {
this.showCustomSnoozeModal = true;
} else {
this.toggleStatus(
wootConstants.STATUS_TYPE.SNOOZED,
findSnoozeTime(snoozeType) || null
);
}
},
chooseSnoozeTime(customSnoozeTime) {
this.showCustomSnoozeModal = false;
if (customSnoozeTime) {
this.toggleStatus(
wootConstants.STATUS_TYPE.SNOOZED,
getUnixTime(customSnoozeTime)
);
}
},
toggleStatus(status, snoozedUntil) {
this.$store
.dispatch('toggleStatus', {
conversationId: this.currentChat?.id || this.contextMenuChatId,
status,
snoozedUntil,
})
.then(() => {
this.$store.dispatch('setContextMenuChatId', null);
this.showAlert(this.$t('CONVERSATION.CHANGE_STATUS'));
});
},
hideCustomSnoozeModal() {
// if we select custom snooze and then the custom snooze modal is open
// Then if the custom snooze modal is closed and set the context menu chat id to null
this.$store.dispatch('setContextMenuChatId', null);
this.showCustomSnoozeModal = false;
},
},
};
</script>
@@ -1,115 +0,0 @@
<script setup>
import { computed } from 'vue';
import ConversationBasicFilter from './widgets/conversation/ConversationBasicFilter.vue';
const props = defineProps({
pageTitle: {
type: String,
required: true,
},
hasAppliedFilters: {
type: Boolean,
required: true,
},
hasActiveFolders: {
type: Boolean,
required: true,
},
activeStatus: {
type: String,
required: true,
},
});
const emits = defineEmits([
'add-folders',
'delete-folders',
'reset-filters',
'basic-filter-change',
'filters-modal',
]);
const onBasicFilterChange = (value, type) => {
emits('basic-filter-change', value, type);
};
const hasAppliedFiltersOrActiveFolders = computed(() => {
return props.hasAppliedFilters || props.hasActiveFolders;
});
</script>
<template>
<div
class="flex items-center justify-between px-4 py-0"
:class="{
'pb-3 border-b border-slate-75 dark:border-slate-700':
hasAppliedFiltersOrActiveFolders,
}"
>
<div class="flex max-w-[85%] justify-center items-center">
<h1
class="text-xl font-medium break-words truncate text-black-900 dark:text-slate-100"
:title="pageTitle"
>
{{ pageTitle }}
</h1>
<span
v-if="!hasAppliedFiltersOrActiveFolders"
class="p-1 my-0.5 mx-1 rounded-md capitalize bg-slate-50 dark:bg-slate-800 text-xxs text-slate-600 dark:text-slate-300"
>
{{ $t(`CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.${activeStatus}.TEXT`) }}
</span>
</div>
<div class="flex items-center gap-1">
<div v-if="hasAppliedFilters && !hasActiveFolders">
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.ADD.SAVE_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="secondary"
icon="save"
@click="emits('add-folders')"
/>
<woot-button
v-tooltip.top-end="$t('FILTER.CLEAR_BUTTON_LABEL')"
size="tiny"
variant="smooth"
color-scheme="alert"
icon="dismiss-circle"
@click="emits('reset-filters')"
/>
</div>
<div v-if="hasActiveFolders">
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.EDIT.EDIT_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="secondary"
icon="edit"
@click="emits('filters-modal')"
/>
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.DELETE.DELETE_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="alert"
icon="delete"
@click="emits('delete-folders')"
/>
</div>
<woot-button
v-else
v-tooltip.right="$t('FILTER.TOOLTIP_LABEL')"
variant="smooth"
color-scheme="secondary"
icon="filter"
size="tiny"
@click="emits('filters-modal')"
/>
<conversation-basic-filter
v-if="!hasAppliedFiltersOrActiveFolders"
@changeFilter="onBasicFilterChange"
/>
</div>
</div>
</template>
@@ -73,13 +73,25 @@
</woot-dropdown-item>
</woot-dropdown-menu>
</div>
<woot-modal
:show.sync="showCustomSnoozeModal"
:on-close="hideCustomSnoozeModal"
>
<custom-snooze-modal
@close="hideCustomSnoozeModal"
@choose-time="chooseSnoozeTime"
/>
</woot-modal>
</div>
</template>
<script>
import { getUnixTime } from 'date-fns';
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
@@ -87,12 +99,14 @@ import wootConstants from 'dashboard/constants/globals';
import {
CMD_REOPEN_CONVERSATION,
CMD_RESOLVE_CONVERSATION,
CMD_SNOOZE_CONVERSATION,
} from '../../routes/dashboard/commands/commandBarBusEvents';
export default {
components: {
WootDropdownItem,
WootDropdownMenu,
CustomSnoozeModal,
},
mixins: [alertMixin, keyboardEventListenerMixins],
props: { conversationId: { type: [String, Number], required: true } },
@@ -101,6 +115,7 @@ export default {
isLoading: false,
showActionsDropdown: false,
STATUS_TYPE: wootConstants.STATUS_TYPE,
showCustomSnoozeModal: false,
};
},
computed: {
@@ -128,10 +143,12 @@ export default {
},
},
mounted() {
bus.$on(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
bus.$on(CMD_REOPEN_CONVERSATION, this.onCmdOpenConversation);
bus.$on(CMD_RESOLVE_CONVERSATION, this.onCmdResolveConversation);
},
destroyed() {
bus.$off(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
bus.$off(CMD_REOPEN_CONVERSATION, this.onCmdOpenConversation);
bus.$off(CMD_RESOLVE_CONVERSATION, this.onCmdResolveConversation);
},
@@ -184,6 +201,28 @@ export default {
// error
}
},
onCmdSnoozeConversation(snoozeType) {
if (snoozeType === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) {
this.showCustomSnoozeModal = true;
} else {
this.toggleStatus(
this.STATUS_TYPE.SNOOZED,
findSnoozeTime(snoozeType) || null
);
}
},
chooseSnoozeTime(customSnoozeTime) {
this.showCustomSnoozeModal = false;
if (customSnoozeTime) {
this.toggleStatus(
this.STATUS_TYPE.SNOOZED,
getUnixTime(customSnoozeTime)
);
}
},
hideCustomSnoozeModal() {
this.showCustomSnoozeModal = false;
},
onCmdOpenConversation() {
this.toggleStatus(this.STATUS_TYPE.OPEN);
},
@@ -7,7 +7,7 @@
:header-title="$t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS')"
:header-content="$t('SIDEBAR_ITEMS.SELECTOR_SUBTITLE')"
/>
<div class="px-8 py-4">
<div class="px-8 pt-4 pb-8">
<div
v-for="account in currentUser.accounts"
:id="`account-${account.id}`"
@@ -45,10 +45,10 @@
<div
v-if="globalConfig.createNewAccountFromDashboard"
class="flex justify-end items-center px-8 pb-8 pt-4 gap-2"
class="flex justify-end items-center p-8 gap-2"
>
<button
class="button success large expanded nice w-full"
class="button success large expanded nice"
@click="$emit('show-create-account-modal')"
>
{{ $t('CREATE_ACCOUNT.NEW_ACCOUNT') }}
@@ -103,7 +103,6 @@
:status="chat.status"
:inbox-id="inbox.id"
:priority="chat.priority"
:chat-id="chat.id"
:has-unread-messages="hasUnread"
@update-conversation="onUpdateConversation"
@assign-agent="onAssignAgent"
@@ -16,7 +16,7 @@
/>
</template>
<menu-item
v-if="showSnooze"
v-if="show(snoozeOption.key)"
:option="snoozeOption"
variant="icon"
@click="snoozeConversation()"
@@ -86,10 +86,6 @@ export default {
},
mixins: [agentMixin],
props: {
chatId: {
type: Number,
default: null,
},
status: {
type: String,
default: '',
@@ -209,10 +205,6 @@ export default {
...this.filteredAgentOnAvailability,
];
},
showSnooze() {
// Don't show snooze if the conversation is already snoozed/resolved/pending
return this.status === wootConstants.STATUS_TYPE.OPEN;
},
},
mounted() {
this.$store.dispatch('inboxAssignableAgents/fetch', [this.inboxId]);
@@ -221,8 +213,7 @@ export default {
toggleStatus(status, snoozedUntil) {
this.$emit('update-conversation', status, snoozedUntil);
},
async snoozeConversation() {
await this.$store.dispatch('setContextMenuChatId', this.chatId);
snoozeConversation() {
const ninja = document.querySelector('ninja-keys');
ninja.open({ parent: 'snooze_conversation' });
},
@@ -1,16 +1,9 @@
<template>
<div class="phone-input--wrap relative">
<div
class="flex items-center dark:bg-slate-900 justify-start rounded-md border border-solid"
:class="
error
? 'border border-solid border-red-400 dark:border-red-400 mb-1'
: 'mb-4 border-slate-200 dark:border-slate-600'
"
>
<div class="phone-input" :class="{ 'has-error': error }">
<div
class="cursor-pointer py-2 pr-1.5 pl-2 rounded-tl-md rounded-bl-md flex items-center justify-center gap-1.5 bg-slate-25 dark:bg-slate-700 h-10 w-14"
@click.prevent="toggleCountryDropdown"
@click="toggleCountryDropdown"
>
<h5 v-if="activeCountry" class="mb-0">
{{ activeCountry.emoji }}
@@ -20,15 +13,14 @@
</div>
<span
v-if="activeDialCode"
class="flex bg-white dark:bg-slate-900 text-slate-800 dark:text-slate-100 font-normal text-base leading-normal py-2 pl-2 pr-0"
class="flex bg-white dark:bg-slate-900 font-medium text-slate-800 dark:text-slate-100 font-normal text-base leading-normal py-2 pl-2 pr-0"
>
{{ activeDialCode }}
</span>
<input
ref="phoneNumberInput"
:value="phoneNumber"
type="tel"
class="!mb-0 !rounded-tl-none !rounded-bl-none !border-0 font-normal !w-full dark:!bg-slate-900 text-base !px-1.5 placeholder:font-normal"
class="phone-input--field"
:placeholder="placeholder"
:readonly="readonly"
:style="styles"
@@ -36,36 +28,24 @@
@blur="onBlur"
/>
</div>
<div
v-if="showDropdown"
ref="dropdown"
v-on-clickaway="onOutsideClick"
tabindex="0"
class="z-10 absolute h-60 w-[12.5rem] shadow-md overflow-y-auto top-10 rounded px-0 pt-0 pb-1 bg-white dark:bg-slate-900"
@keydown.prevent.up="moveUp"
@keydown.prevent.down="moveDown"
@keydown.prevent.enter="
onSelectCountry(filteredCountriesBySearch[selectedIndex])
"
>
<div class="top-0 sticky bg-white dark:bg-slate-900 p-1">
<div v-if="showDropdown" ref="dropdown" class="country-dropdown">
<div class="dropdown-search--wrap">
<input
ref="searchbar"
v-model="searchCountry"
type="text"
placeholder="Search"
class="!h-8 !mb-0 !text-sm !border !border-solid !border-slate-200 dark:!border-slate-600"
@input="onSearchCountry"
class="dropdown-search"
/>
</div>
<div
v-for="(country, index) in filteredCountriesBySearch"
ref="dropdownItem"
:key="index"
class="flex items-center h-7 py-0 px-1 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-700"
class="country-dropdown--item"
:class="{
'bg-slate-50 dark:bg-slate-700': country.id === activeCountryCode,
'bg-slate-25 dark:bg-slate-800': index === selectedIndex,
active: country.id === activeCountryCode,
focus: index === selectedIndex,
}"
@click="onSelectCountry(country)"
>
@@ -94,8 +74,10 @@
<script>
import countries from 'shared/constants/countries.js';
import parsePhoneNumber from 'libphonenumber-js';
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
export default {
mixins: [keyboardEventListenerMixins],
props: {
value: {
type: [String, Number],
@@ -120,6 +102,15 @@ export default {
},
data() {
return {
countries: [
{
name: 'Select Country',
dial_code: '',
emoji: '',
id: '',
},
...countries,
],
selectedIndex: -1,
showDropdown: false,
searchCountry: '',
@@ -129,20 +120,6 @@ export default {
};
},
computed: {
countries() {
return [
{
name: this.dropdownFirstItemName,
dial_code: '',
emoji: '',
id: '',
},
...countries,
];
},
dropdownFirstItemName() {
return this.activeCountryCode ? 'Clear selection' : 'Select Country';
},
filteredCountriesBySearch() {
return this.countries.filter(country => {
const { name, dial_code, id } = country;
@@ -177,8 +154,12 @@ export default {
},
},
mounted() {
window.addEventListener('mouseup', this.onOutsideClick);
this.setActiveCountry();
},
beforeDestroy() {
window.removeEventListener('mouseup', this.onOutsideClick);
},
methods: {
onOutsideClick(e) {
if (
@@ -196,14 +177,28 @@ export default {
onBlur(e) {
this.$emit('blur', e.target.value);
},
onSearchCountry() {
// Reset selected index to 0
this.selectedIndex = 0;
dropdownItem() {
if (!this.showDropdown) return [];
return Array.from(
this.$refs.dropdown.querySelectorAll(
'div.country-dropdown div.country-dropdown--item'
)
);
},
focusedItem() {
if (!this.showDropdown) return [];
return Array.from(
this.$refs.dropdown.querySelectorAll('div.country-dropdown div.focus')
);
},
focusedItemIndex() {
if (!this.showDropdown) return -1;
return Array.from(this.dropdownItem()).indexOf(this.focusedItem()[0]);
},
moveUp() {
if (!this.showDropdown) return;
this.selectedIndex = Math.max(this.selectedIndex - 1, 0);
this.scrollToSelected();
this.$refs.dropdown.scrollTop = this.focusedItemIndex() * 28 - 56;
},
moveDown() {
if (!this.showDropdown) return;
@@ -211,27 +206,14 @@ export default {
this.selectedIndex + 1,
this.filteredCountriesBySearch.length - 1
);
this.scrollToSelected();
},
scrollToSelected() {
this.$nextTick(() => {
const dropdown = this.$refs.dropdown;
const selectedItem = this.$refs.dropdownItem[this.selectedIndex];
const dropdownSearchbarHeight = 40;
if (selectedItem) {
const selectedItemTop = selectedItem.offsetTop;
dropdown.scrollTop = selectedItemTop - dropdownSearchbarHeight;
}
});
this.$refs.dropdown.scrollTop = this.focusedItemIndex() * 28 - 56;
},
onSelectCountry(country) {
if (!country || !this.showDropdown) return;
this.activeCountryCode = country.id;
this.searchCountry = '';
this.activeDialCode = country.dial_code;
this.$emit('setCode', country.dial_code);
this.closeDropdown();
this.$refs.phoneNumberInput.focus();
},
setActiveCountry() {
const { phoneNumber } = this;
@@ -242,6 +224,33 @@ export default {
this.activeDialCode = number.countryCallingCode;
}
},
getKeyboardEvents() {
return {
ArrowUp: {
action: e => {
e.preventDefault();
this.moveUp();
},
allowOnFocusedInput: true,
},
ArrowDown: {
action: e => {
e.preventDefault();
this.moveDown();
},
allowOnFocusedInput: true,
},
Enter: {
action: e => {
e.preventDefault();
this.onSelectCountry(
this.filteredCountriesBySearch[this.selectedIndex]
);
},
allowOnFocusedInput: true,
},
};
},
toggleCountryDropdown() {
this.showDropdown = !this.showDropdown;
this.selectedIndex = -1;
@@ -258,3 +267,46 @@ export default {
},
};
</script>
<style scoped lang="scss">
.phone-input--wrap {
.phone-input {
@apply flex items-center dark:bg-slate-900 justify-start mb-4 rounded-md border border-solid border-slate-200 dark:border-slate-600;
&.has-error {
@apply border border-solid border-red-400 dark:border-red-400;
}
}
.phone-input--field {
@apply mb-0 rounded-tl-none rounded-bl-none border-0 w-full dark:bg-slate-900 text-base px-1.5;
&::placeholder {
@apply font-normal;
}
}
.country-dropdown {
@apply z-10 absolute h-60 w-[12.5rem] shadow-md overflow-y-auto top-10 rounded px-0 pt-0 pb-1 bg-white dark:bg-slate-900;
.dropdown-search--wrap {
@apply top-0 sticky bg-white dark:bg-slate-900 p-1;
.dropdown-search {
@apply h-8 mb-0 text-sm border border-solid border-slate-200 dark:border-slate-600;
}
}
.country-dropdown--item {
@apply flex items-center h-7 py-0 px-1 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-700;
&.active {
@apply bg-slate-50 dark:bg-slate-700;
}
&.focus {
@apply bg-slate-25 dark:bg-slate-800;
}
}
}
}
</style>
@@ -52,22 +52,8 @@ export const validateLoggedInRoutes = (to, user, roleWiseRoutes) => {
return null;
};
export const isAConversationRoute = (
routeName,
includeBase = false,
includeExtended = true
) => {
const baseRoutes = [
'home',
'conversation_mentions',
'conversation_unattended',
'inbox_dashboard',
'label_conversations',
'team_conversations',
'folder_conversations',
'conversation_participating',
];
const extendedRoutes = [
export const isAConversationRoute = routeName =>
[
'inbox_conversation',
'conversation_through_mentions',
'conversation_through_unattended',
@@ -76,15 +62,7 @@ export const isAConversationRoute = (
'conversations_through_team',
'conversations_through_folders',
'conversation_through_participating',
];
const routes = [
...(includeBase ? baseRoutes : []),
...(includeExtended ? extendedRoutes : []),
];
return routes.includes(routeName);
};
].includes(routeName);
export const getConversationDashboardRoute = routeName => {
switch (routeName) {
@@ -66,29 +66,3 @@ export const snoozedReopenTime = snoozedUntil => {
}
return snoozedUntil ? format(date, 'd MMM, h.mmaaa') : null;
};
export const snoozedReopenTimeToTimestamp = snoozedUntil => {
return snoozedUntil ? getUnixTime(new Date(snoozedUntil)) : null;
};
export const shortenSnoozeTime = snoozedUntil => {
if (!snoozedUntil) {
return null;
}
const unitMap = {
minutes: 'm',
minute: 'm',
hours: 'h',
hour: 'h',
days: 'd',
day: 'd',
months: 'mo',
month: 'mo',
years: 'y',
year: 'y',
};
const shortenTime = snoozedUntil.replace(
/\s(minute|hour|day|month|year)s?\b/gi,
(match, unit) => unitMap[unit.toLowerCase()] || match
);
return shortenTime;
};
@@ -106,51 +106,6 @@ describe('isAConversationRoute', () => {
expect(isAConversationRoute('conversations_through_team')).toBe(true);
expect(isAConversationRoute('dashboard')).toBe(false);
});
it('returns true if base conversation route name is provided and includeBase is true', () => {
expect(isAConversationRoute('home', true)).toBe(true);
expect(isAConversationRoute('conversation_mentions', true)).toBe(true);
expect(isAConversationRoute('conversation_unattended', true)).toBe(true);
expect(isAConversationRoute('inbox_dashboard', true)).toBe(true);
expect(isAConversationRoute('label_conversations', true)).toBe(true);
expect(isAConversationRoute('team_conversations', true)).toBe(true);
expect(isAConversationRoute('folder_conversations', true)).toBe(true);
expect(isAConversationRoute('conversation_participating', true)).toBe(true);
});
it('returns false if base conversation route name is provided and includeBase is false', () => {
expect(isAConversationRoute('home', false)).toBe(false);
expect(isAConversationRoute('conversation_mentions', false)).toBe(false);
expect(isAConversationRoute('conversation_unattended', false)).toBe(false);
expect(isAConversationRoute('inbox_dashboard', false)).toBe(false);
expect(isAConversationRoute('label_conversations', false)).toBe(false);
expect(isAConversationRoute('team_conversations', false)).toBe(false);
expect(isAConversationRoute('folder_conversations', false)).toBe(false);
expect(isAConversationRoute('conversation_participating', false)).toBe(
false
);
});
it('returns true if base conversation route name is provided and includeBase and includeExtended is true', () => {
expect(isAConversationRoute('home', true, true)).toBe(true);
expect(isAConversationRoute('conversation_mentions', true, true)).toBe(
true
);
expect(isAConversationRoute('conversation_unattended', true, true)).toBe(
true
);
expect(isAConversationRoute('inbox_dashboard', true, true)).toBe(true);
expect(isAConversationRoute('label_conversations', true, true)).toBe(true);
expect(isAConversationRoute('team_conversations', true, true)).toBe(true);
expect(isAConversationRoute('folder_conversations', true, true)).toBe(true);
expect(isAConversationRoute('conversation_participating', true, true)).toBe(
true
);
});
it('returns false if base conversation route name is not provided', () => {
expect(isAConversationRoute('')).toBe(false);
});
});
describe('getConversationDashboardRoute', () => {
@@ -5,8 +5,6 @@ import {
findStartOfNextMonth,
findNextDay,
setHoursToNine,
snoozedReopenTimeToTimestamp,
shortenSnoozeTime,
} from '../snoozeHelpers';
describe('#Snooze Helpers', () => {
@@ -109,41 +107,4 @@ describe('#Snooze Helpers', () => {
expect(findNextDay(today)).toEqual(nextDay);
});
});
describe('snoozedReopenTimeToTimestamp', () => {
it('should return timestamp if snoozedUntil is not nil', () => {
expect(snoozedReopenTimeToTimestamp('2023-06-07T09:00:00.000Z')).toEqual(
1686128400
);
});
it('should return nil if snoozedUntil is nil', () => {
expect(snoozedReopenTimeToTimestamp(null)).toEqual(null);
});
});
describe('shortenSnoozeTime', () => {
it('should return shortened time if snoozedUntil is not nil and day is passed', () => {
expect(shortenSnoozeTime('1 day')).toEqual('1d');
});
it('should return shortened time if snoozedUntil is not nil and month is passed', () => {
expect(shortenSnoozeTime('1 month')).toEqual('1mo');
});
it('should return shortened time if snoozedUntil is not nil and year is passed', () => {
expect(shortenSnoozeTime('1 year')).toEqual('1y');
});
it('should return shortened time if snoozedUntil is not nil and hour is passed', () => {
expect(shortenSnoozeTime('1 hour')).toEqual('1h');
});
it('should return shortened time if snoozedUntil is not nil and minutes is passed', () => {
expect(shortenSnoozeTime('1 minutes')).toEqual('1m');
});
it('should return nil if snoozedUntil is nil', () => {
expect(shortenSnoozeTime(null)).toEqual(null);
});
});
});
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Loading agents",
"UPDATE": {
"CHANGE_STATUS": "Change status",
"SNOOZE_UNTIL": "Snooze",
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply.",
"UPDATE_SUCCESFUL": "Conversation status updated successfully.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -84,7 +84,6 @@
"CONFIRM": {
"TITLE": "Export Contacts",
"MESSAGE": "Are you sure you want to export all contacts?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "Yes, Export",
"NO": "No, Cancel"
}
@@ -107,7 +107,6 @@
"GENERAL": "General",
"REPORTS": "Reports",
"CONVERSATION": "Conversation",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "Change Assignee",
"CHANGE_PRIORITY": "Change Priority",
"CHANGE_TEAM": "Change Team",
@@ -109,8 +109,7 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
}
},
"API": {
"UPDATE_SUCCESS": "Your notification preferences are updated successfully",
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "جاري تحميل الوكلاء",
"UPDATE": {
"CHANGE_STATUS": "تغيير الحالة",
"SNOOZE_UNTIL": "غفوة",
"SNOOZE_UNTIL_NEXT_REPLY": "غفوة حتى الرد القادم.",
"UPDATE_SUCCESFUL": "تم تحديث حالة المحادثة بنجاح.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -84,7 +84,6 @@
"CONFIRM": {
"TITLE": "تصدير جهات الاتصال",
"MESSAGE": "Are you sure you want to export all contacts?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "نعم, قم بالتصدير",
"NO": "نعم, إلغاء"
}
@@ -107,7 +107,6 @@
"GENERAL": "عام",
"REPORTS": "التقارير",
"CONVERSATION": "المحادثات",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "تغيير المحال إليه",
"CHANGE_PRIORITY": "تغيير الأولوية",
"CHANGE_TEAM": "تغيير الفريق",
@@ -109,8 +109,7 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
}
},
"API": {
"UPDATE_SUCCESS": "يتم تحديث إعدادات الإشعارات بنجاح",
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Loading agents",
"UPDATE": {
"CHANGE_STATUS": "Change status",
"SNOOZE_UNTIL": "Snooze",
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply.",
"UPDATE_SUCCESFUL": "Conversation status updated successfully.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -84,7 +84,6 @@
"CONFIRM": {
"TITLE": "Export Contacts",
"MESSAGE": "Are you sure you want to export all contacts?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "Yes, Export",
"NO": "No, Cancel"
}
@@ -107,7 +107,6 @@
"GENERAL": "General",
"REPORTS": "Reports",
"CONVERSATION": "Разговор",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "Change Assignee",
"CHANGE_PRIORITY": "Change Priority",
"CHANGE_TEAM": "Change Team",
@@ -109,8 +109,7 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
}
},
"API": {
"UPDATE_SUCCESS": "Your notification preferences are updated successfully",
@@ -1,17 +1,17 @@
{
"FILTER": {
"TITLE": "Filtre de converses",
"SUBTITLE": "Afegiu els vostres filtres a continuació i premeu 'Aplicar filtres' per eliminar el desordre del xat.",
"EDIT_CUSTOM_FILTER": "Edita la carpeta",
"CUSTOM_VIEWS_SUBTITLE": "Afegeix o elimina filtres i actualitza la teva carpeta.",
"ADD_NEW_FILTER": "Afegeix un filtre",
"FILTER_DELETE_ERROR": "Vaja, sembla que no podem desar res! Afegiu almenys un filtre per desar-lo.",
"SUBTITLE": "Add your filters below and hit 'Apply filters' to cut through the chat clutter.",
"EDIT_CUSTOM_FILTER": "Edit Folder",
"CUSTOM_VIEWS_SUBTITLE": "Add or remove filters and update your folder.",
"ADD_NEW_FILTER": "Add filter",
"FILTER_DELETE_ERROR": "Oops, looks like we can't save nothing! Please add at least one filter to save it.",
"SUBMIT_BUTTON_LABEL": "Aplicar filtres",
"UPDATE_BUTTON_LABEL": "Actualitza la carpeta",
"UPDATE_BUTTON_LABEL": "Update folder",
"CANCEL_BUTTON_LABEL": "Cancel·la",
"CLEAR_BUTTON_LABEL": "Esborra els filtres",
"FOLDER_LABEL": "Nom de la carpeta",
"FOLDER_QUERY_LABEL": "Consulta de carpeta",
"CLEAR_BUTTON_LABEL": "Clear filters",
"FOLDER_LABEL": "Folder Name",
"FOLDER_QUERY_LABEL": "Folder Query",
"EMPTY_VALUE_ERROR": "El valor és necessari.",
"TOOLTIP_LABEL": "Filtre de converses",
"QUERY_DROPDOWN_LABELS": {
@@ -27,8 +27,8 @@
"is_not_present": "No és present",
"is_greater_than": "És més gran que",
"is_less_than": "És més petit que",
"days_before": "És x dies abans",
"starts_with": "Comença amb"
"days_before": "Is x days before",
"starts_with": "Starts with"
},
"ATTRIBUTE_LABELS": {
"TRUE": "Cert",
@@ -36,66 +36,66 @@
},
"ATTRIBUTES": {
"STATUS": "Estat",
"ASSIGNEE_NAME": "Nom assignat",
"INBOX_NAME": "Nom de la safata d'entrada",
"TEAM_NAME": "Nom de l'equip",
"CONVERSATION_IDENTIFIER": "Identificador de conversa",
"CAMPAIGN_NAME": "Nom de la campanya",
"ASSIGNEE_NAME": "Assignee name",
"INBOX_NAME": "Inbox name",
"TEAM_NAME": "Team name",
"CONVERSATION_IDENTIFIER": "Conversation identifier",
"CAMPAIGN_NAME": "Campaign name",
"LABELS": "Etiquetes",
"BROWSER_LANGUAGE": "Idioma del navegador",
"PRIORITY": "Prioritat",
"COUNTRY_NAME": "Nom del país",
"REFERER_LINK": "Enllaç de referència",
"CUSTOM_ATTRIBUTE_LIST": "Llista",
"BROWSER_LANGUAGE": "Browser language",
"PRIORITY": "Priority",
"COUNTRY_NAME": "Country name",
"REFERER_LINK": "Referer link",
"CUSTOM_ATTRIBUTE_LIST": "List",
"CUSTOM_ATTRIBUTE_TEXT": "Llista",
"CUSTOM_ATTRIBUTE_NUMBER": "Número",
"CUSTOM_ATTRIBUTE_LINK": "Enllaç",
"CUSTOM_ATTRIBUTE_CHECKBOX": "Casella de selecció",
"CREATED_AT": "Creat a",
"LAST_ACTIVITY": "Última activitat"
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
"CREATED_AT": "Created at",
"LAST_ACTIVITY": "Last activity"
},
"GROUPS": {
"STANDARD_FILTERS": "Filtres estàndard",
"ADDITIONAL_FILTERS": "Filtres addicionals",
"CUSTOM_ATTRIBUTES": "Atributs personalitzats"
"STANDARD_FILTERS": "Standard filters",
"ADDITIONAL_FILTERS": "Additional filters",
"CUSTOM_ATTRIBUTES": "Custom attributes"
},
"CUSTOM_VIEWS": {
"ADD": {
"TITLE": "Vols desar aquest filtre?",
"LABEL": "Anomena aquest filtre",
"PLACEHOLDER": "Anomena el teu filtre per referir-lo més endavant.",
"ERROR_MESSAGE": "El nom és obligatori.",
"SAVE_BUTTON": "Desa el filtre",
"TITLE": "Do you want to save this filter?",
"LABEL": "Name this filter",
"PLACEHOLDER": "Name your filter to refer it later.",
"ERROR_MESSAGE": "Name is required.",
"SAVE_BUTTON": "Save filter",
"CANCEL_BUTTON": "Cancel·la",
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Carpeta creada correctament.",
"ERROR_MESSAGE": "S'ha produït un error en crear la carpeta."
"SUCCESS_MESSAGE": "Folder created successfully.",
"ERROR_MESSAGE": "Error while creating folder."
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment creat correctament.",
"ERROR_MESSAGE": "S'ha produït un error en crear el segment."
"SUCCESS_MESSAGE": "Segment created successfully.",
"ERROR_MESSAGE": "Error while creating segment."
}
},
"EDIT": {
"EDIT_BUTTON": "Editar la carpeta"
"EDIT_BUTTON": "Edit folder"
},
"DELETE": {
"DELETE_BUTTON": "Suprimir el filtre",
"DELETE_BUTTON": "Delete filter",
"MODAL": {
"CONFIRM": {
"TITLE": "Confirmar la supressió",
"MESSAGE": "Esteu segur que suprimiu el filtre ",
"YES": "Sí, esborra",
"NO": "No, guarda-ho"
"TITLE": "Confirm deletion",
"MESSAGE": "Are you sure to delete the filter ",
"YES": "Yes, delete",
"NO": "No, keep it"
}
},
"API_FOLDERS": {
"SUCCESS_MESSAGE": "La carpeta s'ha suprimit correctament.",
"ERROR_MESSAGE": "S'ha produït un error en suprimir la carpeta."
"SUCCESS_MESSAGE": "Folder deleted successfully.",
"ERROR_MESSAGE": "Error while deleting folder."
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "El segment s'ha suprimit correctament.",
"ERROR_MESSAGE": "S'ha produït un error en suprimir el segment."
"SUCCESS_MESSAGE": "Segment deleted successfully.",
"ERROR_MESSAGE": "Error while deleting segment."
}
}
}
@@ -1,7 +1,7 @@
{
"AGENT_BOTS": {
"HEADER": "Bots",
"LOADING_EDITOR": "S'està carregant l'editor...",
"LOADING_EDITOR": "Loading editor...",
"HEADER_BTN_TXT": "Add bot configuration",
"SIDEBAR_TXT": "<p><b>Agent Bots</b> <p>Agent Bots are like the most fabulous members of your team. They can handle the small stuff, so you can focus on the stuff that matters. Give them a try.</p> <p> You can manage your bots from this page or create new ones using the 'Add bot configuraton' button.</p> <p> Open the <a href=\"https://www.chatwoot.com/hc/chatwoot-user-guide-cloud-version/articles/1677497472-how-to-use-agent-bots\" target=\"blank\">Agent bots handbook</a> in another tab for a helping hand.</p>",
"CSML_BOT_EDITOR": {
@@ -3,7 +3,7 @@
"HEADER": "Agents",
"HEADER_BTN_TXT": "Afegir Agent",
"LOADING": "S'està recollint la llista d'agents",
"SIDEBAR_TXT": "<p><b>Agents</b></p> <p> Un <b>Agent</b> és membre del vostre equip datenció al client. </p><p> Els agents podran veure i respondre missatges dels teus usuaris. La llista mostra tots els agents que hi ha actualment al teu compte.</p><p> Fer clic a <b>Afegeix Agent</b> per afegir un agent nou. L´agent que afegeixes rebrà un correu electrònic amb un enllaç de confirmació per activar el seu compte, després del qual podrà accedir a Chatwoot i respondre als missatges. </p><p> L´accés a les funcions de Chatwoot es basa en els següents rols.</p><p> <b>Agent</b> - Els agents amb aquesta funció només poden accedir a les bústies d´entrada, als informes i a les converses. Poden assignar converses a altres agents o a ells mateixos i resoldre converses.</p><p> <b>Administrador</b> - L´administrador tindrà accés a totes les funcions de Chatwoot habilitades per al teu compte, inclosa la configuració, juntament amb tots els privilegis dels agents normals.</p>",
"SIDEBAR_TXT": "<p><b>Agents</b></p> <p> Un <b>Agent</b> és membre del vostre equip datenció al client. </p><p> Els agents podran veure i respondre missatges dels teus usuaris. La llista mostra tots els agents que hi ha actualment al teu compte.</p><p> Fer clic a <b>Afegeix Agent</b> per afegir un agent nou. Lagent que afegeixes rebrà un correu electrònic amb un enllaç de confirmació per activar el seu compte, després del qual podrà accedir a Chatwoot i respondre als missatges. </p><p> Laccés a les funcions de Chatwoot es basa en els següents rols.</p><p> <b>Agent</b> - Els agents amb aquesta funció només poden accedir a les bústies dentrada, als informes i a les converses. Poden assignar converses a altres agents o a ells mateixos i resoldre converses.</p><p> <b>Administrador</b> - L'administrador tindrà accés a totes les funcions de Chatwoot habilitades per al teu compte, inclosa la configuració, juntament amb tots els privilegis dels agents normals.</p>",
"AGENT_TYPES": {
"ADMINISTRATOR": "Administrador/a",
"AGENT": "Agent"
@@ -31,7 +31,7 @@
"AGENT_TYPE": {
"LABEL": "Tipus d'Agent",
"PLACEHOLDER": "Selecciona un tipus",
"ERROR": "El rol és necessari"
"ERROR": "El tipus d'Agent és necessari"
},
"EMAIL": {
"LABEL": "Adreça de correu electrònic",
@@ -76,8 +76,8 @@
},
"AGENT_AVAILABILITY": {
"LABEL": "Disponibilitat",
"PLACEHOLDER": "Seleccioneu un estat de disponibilitat",
"ERROR": "Es requereix disponibilitat"
"PLACEHOLDER": "Please select an availability status",
"ERROR": "Availability is required"
},
"SUBMIT": "Editar l'agent"
},
@@ -111,7 +111,7 @@
"PLACEHOLDER": {
"AGENT": "Cerca agents",
"TEAM": "Cerca equips",
"INPUT": "Cerca agents"
"INPUT": "Search for agents"
}
}
}
@@ -11,7 +11,7 @@
"DESC": "Audit Logs are trails for events and actions in a Chatwoot System.",
"TABLE_HEADER": [
"User",
"Temps",
"Action",
"Adreça IP"
]
},
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Loading agents",
"UPDATE": {
"CHANGE_STATUS": "Change status",
"SNOOZE_UNTIL": "Snooze",
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply.",
"UPDATE_SUCCESFUL": "Conversation status updated successfully.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -98,7 +98,7 @@
"CREATED_AT": "Created at"
},
"BUTTONS": {
"ADD": "Afegir",
"ADD": "Add",
"EDIT": "Edita",
"DELETE": "Esborrar"
},
@@ -1,75 +1,75 @@
{
"CANNED_MGMT": {
"HEADER": "Respostes predeterminades",
"HEADER_BTN_TXT": "Afegir resposta predeterminada",
"LOADING": "S'estan recollint les respostes predeterminades...",
"HEADER_BTN_TXT": "Add canned response",
"LOADING": "Fetching canned responses...",
"SEARCH_404": "No hi ha cap resposta que coincideixi amb aquesta consulta.",
"SIDEBAR_TXT": "<p><b>Respostes predeterminades</b> </p><p> Les respostes predeterminades són plantilles de respostes escrites prèviament que us ajuden a respondre ràpidament a una conversa. Per inserir una resposta predefinida durant un xat, els agents poden escriure un codi curt precedit d'un caràcter '/'. </p><p> Pots gestionar les teves respostes predefinides des d'aquesta pàgina o crear-ne de noves mitjançant el botó \"Afegeix una resposta predefinida\".</p><p>Obre el <a target=\"_blank\" href=\"https: //www.chatwoot.com/hc/chatwoot-user-guide-cloud-version/articles/1677501325-how-to-create-saved-reply-templates-with-canned-responses\">Manual de respostes llaunes</a> en una altra pestanya per ajudar-te.</p><p>També, fes una ullada al nou <a href=\"https://www.chatwoot.com/tools/canned-responses-library\" target=\"_blank \">Biblioteca de respostes predeterminades</a>.</p>",
"SIDEBAR_TXT": "<p><b>Canned Responses</b> </p><p> Canned Responses are pre-written reply templates that help you quickly respond to a conversation. To insert a canned response during a chat, agents can type a short code preceded by a '/' character. </p><p> You can manage your canned responses from this page or create new ones using the \"Add canned response\" button.</p><p>Open the <a target=\"_blank\" href=\"https://www.chatwoot.com/hc/chatwoot-user-guide-cloud-version/articles/1677501325-how-to-create-saved-reply-templates-with-canned-responses\">Canned Responses handbook</a> in another tab for a helping hand.</p><p>Also, check out the all-new <a href=\"https://www.chatwoot.com/tools/canned-responses-library\" target=\"_blank\">Canned Responses Library</a>.</p>",
"LIST": {
"404": "No hi ha respostes predeterminades disponibles en aquest compte.",
"TITLE": "Gestiona les respostes predeterminades",
"DESC": "Les respostes predeterminades són plantilles de resposta predefinides que es poden utilitzar per enviar ràpidament respostes a les converses.",
"DESC": "Canned Responses are predefined reply templates which can be used to quickly send out replies to conversations.",
"TABLE_HEADER": [
"Codi curt",
"Short code",
"Contingut",
"Accions"
]
},
"ADD": {
"TITLE": "Afegir resposta predeterminada",
"DESC": "Les respostes predeterminades són plantilles de resposta predefinides que es poden utilitzar per enviar ràpidament respostes a les converses.",
"TITLE": "Add canned response",
"DESC": "Canned Responses are predefined reply templates which can be used to quickly send out replies to conversations.",
"CANCEL_BUTTON_TEXT": "Cancel·la",
"FORM": {
"SHORT_CODE": {
"LABEL": "Codi curt",
"PLACEHOLDER": "Introduïu un codi curt.",
"ERROR": "És necessari el codi curt."
"LABEL": "Short code",
"PLACEHOLDER": "Please enter a short code.",
"ERROR": "Short Code is required."
},
"CONTENT": {
"LABEL": "Missatge",
"PLACEHOLDER": "Escriviu el missatge que voleu desar com a plantilla per utilitzar-lo més tard.",
"ERROR": "El missatge és obligatori."
"PLACEHOLDER": "Please write the message you want to save as a template to use later.",
"ERROR": "Message is required."
},
"SUBMIT": "Envia"
},
"API": {
"SUCCESS_MESSAGE": "Resposta predeterminada afegida correctament.",
"SUCCESS_MESSAGE": "Canned response added successfully.",
"ERROR_MESSAGE": "No s'ha pogut connectar amb el servidor Woot. Torna-ho a provar més endavant"
}
},
"EDIT": {
"TITLE": "Edita la resposta predeterminada",
"TITLE": "Edit canned response",
"CANCEL_BUTTON_TEXT": "Cancel·la",
"FORM": {
"SHORT_CODE": {
"LABEL": "Codi curt",
"PLACEHOLDER": "Introduïu un codi curt.",
"ERROR": "És necessari el codi curt."
"LABEL": "Short code",
"PLACEHOLDER": "Please enter a shortcode.",
"ERROR": "Short code is required."
},
"CONTENT": {
"LABEL": "Missatge",
"PLACEHOLDER": "Escriviu el missatge que voleu desar com a plantilla per utilitzar-lo més tard.",
"ERROR": "El missatge és obligatori."
"PLACEHOLDER": "Please write the message you want to save as a template to use later.",
"ERROR": "Message is required."
},
"SUBMIT": "Envia"
},
"BUTTON_TEXT": "Edita",
"API": {
"SUCCESS_MESSAGE": "Resposta predeterminada actualitzada correctament.",
"SUCCESS_MESSAGE": "Canned response is updated successfully.",
"ERROR_MESSAGE": "No s'ha pogut connectar amb el servidor Woot. Torna-ho a provar més endavant"
}
},
"DELETE": {
"BUTTON_TEXT": "Esborra",
"API": {
"SUCCESS_MESSAGE": "Resposta predeterminada eliminada correctament.",
"SUCCESS_MESSAGE": "Canned response deleted successfully.",
"ERROR_MESSAGE": "No s'ha pogut connectar amb el servidor Woot. Torna-ho a provar més endavant"
},
"CONFIRM": {
"TITLE": "Confirma l'esborrat",
"TITLE": "Confirm deletion",
"MESSAGE": "N'estas segur ",
"YES": "Sí, esborra ",
"NO": "No, mantén-la "
"YES": "Yes, delete ",
"NO": "No, keep "
}
}
}
@@ -8,7 +8,7 @@
},
"TAB_HEADING": "Converses",
"MENTION_HEADING": "Mencions",
"UNATTENDED_HEADING": "Sense assistència",
"UNATTENDED_HEADING": "Unattended",
"SEARCH": {
"INPUT": "Cerca persones, xats, respostes desades .."
},
@@ -36,27 +36,27 @@
}
},
"VIEW_FILTER": "Veure",
"SORT_TOOLTIP_LABEL": "Ordena les converses",
"SORT_TOOLTIP_LABEL": "Sort conversations",
"CHAT_SORT": {
"STATUS": "Estat",
"ORDER_BY": "Ordena per"
"ORDER_BY": "Order by"
},
"CHAT_TIME_STAMP": {
"CREATED": {
"LATEST": "Creat",
"OLDEST": "Creat per:"
"LATEST": "Created",
"OLDEST": "Created at:"
},
"LAST_ACTIVITY": {
"NOT_ACTIVE": "Darrera activitat:",
"ACTIVE": "Darrera activitat"
"NOT_ACTIVE": "Last activity:",
"ACTIVE": "Last activity"
}
},
"SORT_ORDER_ITEMS": {
"last_activity_at_asc": {
"TEXT": "Última activitat: Més antic primer"
"TEXT": "Last activity: Oldest first"
},
"last_activity_at_desc": {
"TEXT": "Última activitat: La més nova primer"
"TEXT": "Last activity: Newest first"
},
"created_at_desc": {
"TEXT": "Created at: Newest first"
@@ -99,15 +99,15 @@
},
"CHAT_SORT_BY_FILTER": {
"TITLE": "Sort conversation",
"DROPDOWN_TITLE": "Ordenat per",
"DROPDOWN_TITLE": "Sort by",
"ITEMS": {
"LATEST": {
"NAME": "Last activity at",
"LABEL": "Last activity"
},
"CREATED_AT": {
"NAME": "Creat per",
"LABEL": "Creat per"
"NAME": "Created at",
"LABEL": "Created at"
},
"LAST_USER_MESSAGE_AT": {
"NAME": "Last user message at",
@@ -15,7 +15,7 @@
"INITIATED_FROM": "Iniciada des de",
"INITIATED_AT": "Iniciada a les",
"IP_ADDRESS": "Adreça IP",
"CREATED_AT_LABEL": "Creat",
"CREATED_AT_LABEL": "Created",
"NEW_MESSAGE": "Nou missatge",
"CONVERSATIONS": {
"NO_RECORDS_FOUND": "No hi han converses prèvies associades a aquest contacte.",
@@ -49,7 +49,7 @@
"CUSTOM_ATTRIBUTES": "Atributs personalitzats",
"CONTACT_LABELS": "Etiquetes de contactes",
"PREVIOUS_CONVERSATIONS": "Converses prèvies",
"NO_RECORDS_FOUND": "No s'han trobat atributs"
"NO_RECORDS_FOUND": "No attributes found"
}
},
"EDIT_CONTACT": {
@@ -76,17 +76,16 @@
"ERROR_MESSAGE": "S'ha produït un error; tornau-ho a provar"
},
"EXPORT_CONTACTS": {
"BUTTON_LABEL": "Exporta",
"TITLE": "Exporta contactes",
"DESC": "Exporta contactes a través d'un fitxer CSV.",
"BUTTON_LABEL": "Export",
"TITLE": "Export Contacts",
"DESC": "Export contacts to a CSV file.",
"SUCCESS_MESSAGE": "Export is in progress, You will be notified via email when export file is ready to dowanlod.",
"ERROR_MESSAGE": "S'ha produït un error; tornau-ho a provar",
"CONFIRM": {
"TITLE": "Exporta contactes",
"MESSAGE": "Esteu segur que voleu exportar tots els contactes?",
"FILTERED_MESSAGE": "Esteu segur que voleu exportar tots els contactes filtrats?",
"YES": "Sí, Exporta",
"NO": "No, cancel·la"
"TITLE": "Export Contacts",
"MESSAGE": "Are you sure you want to export all contacts?",
"YES": "Yes, Export",
"NO": "No, Cancel"
}
},
"DELETE_NOTE": {
@@ -152,13 +151,13 @@
"COUNTRY": {
"PLACEHOLDER": "Enter the country name",
"LABEL": "Nom del país",
"SELECT_PLACEHOLDER": "Selecciona",
"SELECT_PLACEHOLDER": "Select",
"REMOVE": "Suprimeix",
"SELECT_COUNTRY": "Selecciona país"
"SELECT_COUNTRY": "Select Country"
},
"CITY": {
"PLACEHOLDER": "Introdueix el nom de la ciutat",
"LABEL": "Nom de la ciutat"
"PLACEHOLDER": "Enter the city name",
"LABEL": "City Name"
},
"SOCIAL_PROFILES": {
"FACEBOOK": {
@@ -181,8 +180,8 @@
},
"DELETE_AVATAR": {
"API": {
"SUCCESS_MESSAGE": "L'avatar del contacte s'ha suprimit correctament",
"ERROR_MESSAGE": "No s'ha pogut esborrar l'avatar de contacte. Torneu-ho a provar."
"SUCCESS_MESSAGE": "Contact avatar deleted successfully",
"ERROR_MESSAGE": "Could not delete the contact avatar. Please try again later."
}
},
"SUCCESS_MESSAGE": "Contacte guardat correctament",
@@ -191,30 +190,30 @@
"NEW_CONVERSATION": {
"BUTTON_LABEL": "Inicia la conversa",
"TITLE": "Nova conversació",
"DESC": "Inicia una conversa nova enviant un missatge nou.",
"DESC": "Start a new conversation by sending a new message.",
"NO_INBOX": "Couldn't find an inbox to initiate a new conversation with this contact.",
"FORM": {
"TO": {
"LABEL": "Per"
"LABEL": "To"
},
"INBOX": {
"LABEL": "Inbox",
"PLACEHOLDER": "Trieu la safata d'entrada d'origen",
"PLACEHOLDER": "Choose source inbox",
"ERROR": "Select an inbox"
},
"SUBJECT": {
"LABEL": "Assumpte",
"PLACEHOLDER": "Assumpte",
"LABEL": "Subject",
"PLACEHOLDER": "Subject",
"ERROR": "Subject can't be empty"
},
"MESSAGE": {
"LABEL": "Missatge",
"PLACEHOLDER": "Write your message here",
"ERROR": "El missatge no pot estar buit"
"ERROR": "Message can't be empty"
},
"ATTACHMENTS": {
"SELECT": "Trieu fitxers",
"HELP_TEXT": "Arrossegueu i deixeu anar fitxers aquí o trieu fitxers per adjuntar"
"SELECT": "Choose files",
"HELP_TEXT": "Drag and drop files here or choose files to attach"
},
"SUBMIT": "Send message",
"CANCEL": "Cancel·la",
@@ -228,10 +227,10 @@
"FIELDS": "Contact fields",
"SEARCH_BUTTON": "Cercar",
"SEARCH_INPUT_PLACEHOLDER": "Cerca de contactes",
"FILTER_CONTACTS": "Filtre",
"FILTER_CONTACTS_SAVE": "Desa el filtre",
"FILTER_CONTACTS_DELETE": "Suprimir el filtre",
"FILTER_CONTACTS_EDIT": "Edita segment",
"FILTER_CONTACTS": "Filter",
"FILTER_CONTACTS_SAVE": "Save filter",
"FILTER_CONTACTS_DELETE": "Delete filter",
"FILTER_CONTACTS_EDIT": "Edit segment",
"LIST": {
"LOADING_MESSAGE": "Carregant contactes...",
"404": "No hi ha cap contacte que coincideixi amb la vostra cerca 🔍",
@@ -242,8 +241,8 @@
"CONVERSATIONS": "Converses",
"LAST_ACTIVITY": "Last Activity",
"CREATED_AT": "Created At",
"COUNTRY": "País",
"CITY": "Ciutat",
"COUNTRY": "Country",
"CITY": "City",
"SOCIAL_PROFILES": "Social Profiles",
"COMPANY": "Companyia",
"EMAIL_ADDRESS": "Adreça de correu electrònic"
@@ -257,93 +256,93 @@
},
"REMINDER": {
"ADD_BUTTON": {
"BUTTON": "Afegir",
"TITLE": "Shift + Enter per crear una tasca"
"BUTTON": "Add",
"TITLE": "Shift + Enter to create a task"
},
"FOOTER": {
"DUE_DATE": "Data de venciment",
"LABEL_TITLE": "Establir tipus"
"DUE_DATE": "Due date",
"LABEL_TITLE": "Set type"
}
},
"NOTES": {
"FETCHING_NOTES": "S'estan obtenint notes...",
"FETCHING_NOTES": "Fetching notes...",
"NOT_AVAILABLE": "There are no notes created for this contact",
"HEADER": {
"TITLE": "Notes"
},
"LIST": {
"LABEL": "afegeix una nota"
"LABEL": "added a note"
},
"ADD": {
"BUTTON": "Afegir",
"PLACEHOLDER": "Afegeix una nota",
"TITLE": "Shift + Enter per crear una nota"
"BUTTON": "Add",
"PLACEHOLDER": "Add a note",
"TITLE": "Shift + Enter to create a note"
},
"CONTENT_HEADER": {
"DELETE": "Suprimeix la nota"
"DELETE": "Delete note"
}
},
"EVENTS": {
"HEADER": {
"TITLE": "Activitats"
"TITLE": "Activities"
},
"BUTTON": {
"PILL_BUTTON_NOTES": "notes",
"PILL_BUTTON_EVENTS": "esdeveniment",
"PILL_BUTTON_EVENTS": "events",
"PILL_BUTTON_CONVO": "converses"
}
},
"CUSTOM_ATTRIBUTES": {
"ADD_BUTTON_TEXT": "Afegir atributs",
"BUTTON": "Afegir atributs personalitzats",
"NOT_AVAILABLE": "No hi ha atributs personalitzats disponibles per a aquest contacte.",
"ADD_BUTTON_TEXT": "Add attributes",
"BUTTON": "Add custom attribute",
"NOT_AVAILABLE": "There are no custom attributes available for this contact.",
"COPY_SUCCESSFUL": "S'ha copiat al porta-retalls amb èxit",
"SHOW_MORE": "Mostra tots els atributs",
"SHOW_LESS": "Mostra menys atributs",
"SHOW_MORE": "Show all attributes",
"SHOW_LESS": "Show less attributes",
"ACTIONS": {
"COPY": "Copia l'atribut",
"DELETE": "Suprimeix l'atribut",
"EDIT": "Edita l'atribut"
"COPY": "Copy attribute",
"DELETE": "Delete attribute",
"EDIT": "Edit attribute"
},
"ADD": {
"TITLE": "Crear atribut personalitzat",
"DESC": "Afegeix informació personalitzada a aquest contacte."
"TITLE": "Create custom attribute",
"DESC": "Add custom information to this contact."
},
"FORM": {
"CREATE": "Afegir atribut",
"CREATE": "Add attribute",
"CANCEL": "Cancel·la",
"NAME": {
"LABEL": "Personalitza el nom de l'atribut",
"PLACEHOLDER": "P. ex.: identificador de Shopify",
"ERROR": "El nom de l'atribut personalitzat no és vàlid"
"LABEL": "Custom attribute name",
"PLACEHOLDER": "Eg: shopify id",
"ERROR": "Invalid custom attribute name"
},
"VALUE": {
"LABEL": "Attribute value",
"PLACEHOLDER": "Eg: 11901 "
},
"ADD": {
"TITLE": "Crear nou atribut ",
"SUCCESS": "Atribut afegit correctament",
"ERROR": "No es pot afegir l'atribut. Si us plau, intenta-ho més tard"
"TITLE": "Create new attribute ",
"SUCCESS": "Attribute added successfully",
"ERROR": "Unable to add attribute. Please try again later"
},
"UPDATE": {
"SUCCESS": "Atribut actualitzat correctament",
"ERROR": "No es pot actualitzar l'atribut. Si us plau, intenta-ho més tard"
"SUCCESS": "Attribute updated successfully",
"ERROR": "Unable to update attribute. Please try again later"
},
"DELETE": {
"SUCCESS": "Atribut esborrat correctament",
"ERROR": "No es pot esborrar l'atribut. Si us plau, intenta-ho més tard"
"SUCCESS": "Attribute deleted successfully",
"ERROR": "Unable to delete attribute. Please try again later"
},
"ATTRIBUTE_SELECT": {
"TITLE": "Afegir atributs",
"PLACEHOLDER": "Cerca atributs",
"NO_RESULT": "No s'han trobat atributs"
"TITLE": "Add attributes",
"PLACEHOLDER": "Search attributes",
"NO_RESULT": "No attributes found"
},
"ATTRIBUTE_TYPE": {
"LIST": {
"PLACEHOLDER": "Selecciona valor",
"SEARCH_INPUT_PLACEHOLDER": "Cerca valor",
"NO_RESULT": "No s'ha trobat cap resultat"
"PLACEHOLDER": "Select value",
"SEARCH_INPUT_PLACEHOLDER": "Search value",
"NO_RESULT": "No result found"
}
}
},
@@ -366,7 +365,7 @@
"HELP_LABEL": "To be kept"
},
"SUMMARY": {
"TITLE": "Resum",
"TITLE": "Summary",
"DELETE_WARNING": "Contact of <strong>%{primaryContactName}</strong> will be deleted.",
"ATTRIBUTE_WARNING": "Contact details of <strong>%{primaryContactName}</strong> will be copied to <strong>%{parentContactName}</strong>."
},
@@ -374,13 +373,13 @@
"ERROR": "ERROR_MESSAGE"
},
"FORM": {
"SUBMIT": " Fusiona contactes",
"SUBMIT": " Merge contacts",
"CANCEL": "Cancel·la",
"CHILD_CONTACT": {
"ERROR": "Seleccioneu un contacte fill per fusionar"
"ERROR": "Select a child contact to merge"
},
"SUCCESS_MESSAGE": "Contacte fusionat correctament",
"ERROR_MESSAGE": "No s'ha pogut fusionar els contactes, torna-ho a provar!"
"SUCCESS_MESSAGE": "Contact merged successfully",
"ERROR_MESSAGE": "Could not merge contacts, try again!"
}
}
}
@@ -28,24 +28,24 @@
"is_not_present": "No és present",
"is_greater_than": "És més gran que",
"is_lesser_than": "Is lesser than",
"days_before": "És x dies abans"
"days_before": "Is x days before"
},
"ATTRIBUTES": {
"NAME": "Nom",
"EMAIL": "Correu electrònic",
"PHONE_NUMBER": "Número de telèfon",
"IDENTIFIER": "Identifier",
"CITY": "Ciutat",
"COUNTRY": "País",
"CUSTOM_ATTRIBUTE_LIST": "Llista",
"CITY": "City",
"COUNTRY": "Country",
"CUSTOM_ATTRIBUTE_LIST": "List",
"CUSTOM_ATTRIBUTE_TEXT": "Llista",
"CUSTOM_ATTRIBUTE_NUMBER": "Número",
"CUSTOM_ATTRIBUTE_LINK": "Enllaç",
"CUSTOM_ATTRIBUTE_CHECKBOX": "Casella de selecció",
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
"CREATED_AT": "Created At",
"LAST_ACTIVITY": "Last Activity",
"REFERER_LINK": "Referrer link",
"BLOCKED": "Blocat"
"BLOCKED": "Blocked"
},
"GROUPS": {
"STANDARD_FILTERS": "Standard Filters",
@@ -1,11 +1,11 @@
{
"CONVERSATION": {
"SELECT_A_CONVERSATION": "Si us plau, selecciona una conversa al panell de lesquerra",
"CSAT_REPLY_MESSAGE": "Si us plau, valoreu la conversa",
"404": "Ho sentim, no podem trobar la conversa. Torneu-ho a provar",
"SWITCH_VIEW_LAYOUT": "Canvia el disseny",
"DASHBOARD_APP_TAB_MESSAGES": "Missatges",
"UNVERIFIED_SESSION": "La identitat d'aquest usuari no està verificada",
"CSAT_REPLY_MESSAGE": "Please rate the conversation",
"404": "Sorry, we cannot find the conversation. Please try again",
"SWITCH_VIEW_LAYOUT": "Switch the layout",
"DASHBOARD_APP_TAB_MESSAGES": "Messages",
"UNVERIFIED_SESSION": "The identity of this user is not verified",
"NO_MESSAGE_1": "Uh oh! Sembla que no hi ha missatges de clients a la safata d'entrada.",
"NO_MESSAGE_2": " per enviar un missatge a la vostra pàgina!",
"NO_INBOX_1": "Hola! Sembla que encara no heu afegit cap safata d'entrada.",
@@ -13,15 +13,15 @@
"NO_INBOX_AGENT": "Uh Oh! Sembla que no ets a cap safata d'entrada. Si us plau, poseu-vos en contacte amb l'administrador",
"SEARCH_MESSAGES": "Cerca missatges a les converses",
"EMPTY_STATE": {
"CMD_BAR": "per obrir el menú d'ordres",
"KEYBOARD_SHORTCUTS": "per veure les dreceres del teclat"
"CMD_BAR": "to open command menu",
"KEYBOARD_SHORTCUTS": "to view keyboard shortcuts"
},
"SEARCH": {
"TITLE": "Cerca missatges",
"RESULT_TITLE": "Resultats de la cerca",
"RESULT_TITLE": "Search Results",
"LOADING_MESSAGE": "S'estan restringint les dades...",
"PLACEHOLDER": "Escriu qualsevol text per cercar missatges",
"NO_MATCHING_RESULTS": "No s'han trobat resultats."
"NO_MATCHING_RESULTS": "No results found."
},
"UNREAD_MESSAGES": "Missatges no Llegits",
"UNREAD_MESSAGE": "Missatge no Llegit",
@@ -30,15 +30,15 @@
"LOADING_CONVERSATIONS": "S'estan carregant les converses",
"CANNOT_REPLY": "No pots respondre degut a",
"24_HOURS_WINDOW": "Restricció de finestra de missatges de 24 hores",
"NOT_ASSIGNED_TO_YOU": "Aquesta conversa no està assignada a tu. Vols assignar-te-la?",
"ASSIGN_TO_ME": "Assigna'm",
"TWILIO_WHATSAPP_CAN_REPLY": "Només pots respondre a aquesta conversa mitjançant una plantilla de missatge a causa de",
"NOT_ASSIGNED_TO_YOU": "This conversation is not assigned to you. Would you like to assign this conversation to yourself?",
"ASSIGN_TO_ME": "Assign to me",
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "Restricció de finestra de missatges de 24 hores",
"REPLYING_TO": "Estas responent a:",
"REMOVE_SELECTION": "Elimina la selecció",
"DOWNLOAD": "Descarrega",
"UNKNOWN_FILE_TYPE": "Fitxer desconegut",
"SAVE_CONTACT": "Desar",
"UNKNOWN_FILE_TYPE": "Unknown File",
"SAVE_CONTACT": "Save",
"UPLOADING_ATTACHMENTS": "Pujant fitxers adjunts...",
"REPLIED_TO_STORY": "Replied to your story",
"UNSUPPORTED_MESSAGE": "This message is unsupported.",
@@ -89,7 +89,7 @@
"CANCEL": "Cancel·la"
},
"PRIORITY": {
"TITLE": "Prioritat",
"TITLE": "Priority",
"OPTIONS": {
"NONE": "Ningú",
"URGENT": "Urgent",
@@ -280,29 +280,29 @@
},
"CONVERSATION_CUSTOM_ATTRIBUTES": {
"ADD_BUTTON_TEXT": "Create attribute",
"NO_RECORDS_FOUND": "No s'han trobat atributs",
"NO_RECORDS_FOUND": "No attributes found",
"UPDATE": {
"SUCCESS": "Atribut actualitzat correctament",
"ERROR": "No es pot actualitzar l'atribut. Si us plau, intenta-ho més tard"
"SUCCESS": "Attribute updated successfully",
"ERROR": "Unable to update attribute. Please try again later"
},
"ADD": {
"TITLE": "Afegir",
"SUCCESS": "Atribut afegit correctament",
"ERROR": "No es pot afegir l'atribut. Si us plau, intenta-ho més tard"
"TITLE": "Add",
"SUCCESS": "Attribute added successfully",
"ERROR": "Unable to add attribute. Please try again later"
},
"DELETE": {
"SUCCESS": "Atribut esborrat correctament",
"ERROR": "No es pot esborrar l'atribut. Si us plau, intenta-ho més tard"
"SUCCESS": "Attribute deleted successfully",
"ERROR": "Unable to delete attribute. Please try again later"
},
"ATTRIBUTE_SELECT": {
"TITLE": "Afegir atributs",
"PLACEHOLDER": "Cerca atributs",
"NO_RESULT": "No s'han trobat atributs"
"TITLE": "Add attributes",
"PLACEHOLDER": "Search attributes",
"NO_RESULT": "No attributes found"
}
},
"EMAIL_HEADER": {
"FROM": "From",
"TO": "Per",
"TO": "To",
"BCC": "Bcc",
"CC": "Cc",
"SUBJECT": "Subject"
@@ -1,7 +1,7 @@
{
"EMOJI": {
"PLACEHOLDER": "Cerca emojis",
"NOT_FOUND": "Cap emoji coincideix amb la teva cerca",
"PLACEHOLDER": "Search emojis",
"NOT_FOUND": "No emoji match your search",
"REMOVE": "Suprimeix"
}
}
@@ -1,5 +1,5 @@
{
"GENERAL": {
"SHOWING_RESULTS": "Es mostren {firstIndex}-{lastIndex} de {totalCount} elements"
"SHOWING_RESULTS": "Showing {firstIndex}-{lastIndex} of {totalCount} items"
}
}
@@ -3,7 +3,7 @@
"TITLE": "Configuració del compte",
"SUBMIT": "Actualització de la configuració",
"BACK": "Enrere",
"DISMISS": "Descartar",
"DISMISS": "Dismiss",
"UPDATE": {
"ERROR": "No s'ha pogut actualitzar la configuració, torna-ho a provar!",
"SUCCESS": "La configuració del compte s'ha actualitzat correctament"
@@ -15,8 +15,8 @@
"NOTE": ""
},
"ACCOUNT_ID": {
"TITLE": "ID del compte",
"NOTE": "Aquest identificador és necessari si esteu creant una integració basada en API"
"TITLE": "Account ID",
"NOTE": "This ID is required if you are building an API based integration"
},
"NAME": {
"LABEL": "Nom del compte",
@@ -24,7 +24,7 @@
"ERROR": "Introduïu un nom de compte vàlid"
},
"LANGUAGE": {
"LABEL": "Idioma del lloc",
"LABEL": "Site language",
"PLACEHOLDER": "El nom del vostre compte",
"ERROR": ""
},
@@ -41,7 +41,7 @@
"AUTO_RESOLVE_DURATION": {
"LABEL": "El nombre de dies després que un ticket es resolgui automàticament si no hi ha activitat",
"PLACEHOLDER": "30",
"ERROR": "Introduïu una durada de resolució automàtica vàlida (mínim 1 dia i màxim 999 dies)"
"ERROR": "Please enter a valid auto resolve duration (minimum 1 day and maximum 999 days)"
},
"FEATURES": {
"INBOUND_EMAIL_ENABLED": "La continuïtat de converses amb correus electrònics està habilitada per al vostre compte.",
@@ -49,8 +49,8 @@
}
},
"UPDATE_CHATWOOT": "L'actualització %{latestChatwootVersion} per Chatwoot està disponible. Si us plau, actualitza l'instancia.",
"LEARN_MORE": "Aprèn més",
"PAYMENT_PENDING": "El teu pagament està pendent. Actualitzeu la vostra informació de pagament per continuar utilitzant Chatwoot",
"LEARN_MORE": "Learn more",
"PAYMENT_PENDING": "Your payment is pending. Please update your payment information to continue using Chatwoot",
"LIMITS_UPGRADE": "Your account has exceeded the usage limits, please upgrade your plan to continue using Chatwoot",
"OPEN_BILLING": "Open billing"
},
@@ -107,7 +107,6 @@
"GENERAL": "General",
"REPORTS": "Informes",
"CONVERSATION": "Conversation",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "Change Assignee",
"CHANGE_PRIORITY": "Change Priority",
"CHANGE_TEAM": "Change Team",
@@ -460,7 +460,7 @@
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
"SELF_HOSTED_DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Please contact your administrator to enable this feature.",
"BUTTON": {
"LEARN_MORE": "Aprèn més",
"LEARN_MORE": "Learn more",
"UPGRADE": "Upgrade"
},
"FEATURES": {
@@ -42,7 +42,7 @@
"SORT_OPTIONS": {
"NEWEST": "Newest",
"OLDEST": "Oldest",
"PRIORITY": "Prioritat"
"PRIORITY": "Priority"
},
"DISPLAY_OPTIONS": {
"SNOOZED": "Posposat",
@@ -83,7 +83,7 @@
},
"CHANNEL_GREETING_TOGGLE": {
"LABEL": "Activa la salutació del canal",
"HELP_TEXT": "Envia automàticament missatges de salutació quan els clients inicien una conversa i envien el seu primer missatge.",
"HELP_TEXT": "Automatically send a greeting message when a new conversation is created.",
"ENABLED": "Habilita",
"DISABLED": "Inhabilita"
},
@@ -169,7 +169,7 @@
},
"BANDWIDTH": {
"ACCOUNT_ID": {
"LABEL": "ID del compte",
"LABEL": "Account ID",
"PLACEHOLDER": "Please enter your Bandwidth Account ID",
"ERROR": "Aquest camp és obligatori"
},
@@ -340,7 +340,7 @@
},
"AUTH": {
"TITLE": "Choose a channel",
"DESC": "Chatwoot admet widgets de xat en directe, Facebook Messenger, WhatsApp, correus electrònics, etc., com a canals. Si voleu crear un canal personalitzat, podeu crear-lo mitjançant el canal API. Per començar, tria un dels canals següents."
"DESC": "Chatwoot supports live-chat widgets, Facebook Messenger, Twitter profiles, WhatsApp, Emails, etc., as channels. If you want to build a custom channel, you can create it using the API channel. To get started, choose one of the channels below."
},
"AGENTS": {
"TITLE": "Agents",
@@ -420,7 +420,7 @@
"BUSINESS_NAME": {
"BUTTON_TEXT": "+ Configure your business name",
"PLACEHOLDER": "Enter your business name",
"SAVE_BUTTON_TEXT": "Desar"
"SAVE_BUTTON_TEXT": "Save"
}
},
"ALLOW_MESSAGES_AFTER_RESOLVED": {
@@ -68,7 +68,7 @@
"TITLE": "Confirma l'esborrat",
"MESSAGE": "Are you sure to delete the webhook? (%{webhookURL})",
"YES": "Si, esborra ",
"NO": "No, mantén-la"
"NO": "No, manten-la"
}
}
},
@@ -79,8 +79,8 @@
"MESSAGE": "Are you sure you want to delete the integration? Doing so will result in the loss of access to conversations on your Slack workspace."
},
"HELP_TEXT": {
"TITLE": "Com utilitzar la integració de Slack?",
"BODY": "Amb aquesta integració, totes les teves converses entrants se sincronitzaran amb el canal ***%{selectedChannelName}*** del teu espai de treball de Slack. Podeu gestionar totes les converses dels vostres clients directament des del canal i no perdreu cap missatge.\n\nAquestes són les principals característiques de la integració:\n\n**Respondre a converses des de Slack:** Per respondre a una conversa al ***%{selectedChannelName}*** canal de Slack, només has d'escriure el teu missatge i enviar-lo com a fil. Això crearà una resposta al client mitjançant Chatwoot. Així de senzill!\n\n **Crear notes privades:** si vols crear notes privades en lloc de respostes, comença el teu missatge amb ***`nota:`***. Això garanteix que el vostre missatge es mantingui privat i no serà visible per al client.\n\n**Associar un perfil d'agent:** si la persona que va respondre a Slack té un perfil d'agent a Chatwoot amb el mateix correu electrònic, les respostes s'associaran amb aquest perfil d'agent automàticament. Això vol dir que podeu fer un seguiment fàcilment qui va dir què i quan. D'altra banda, quan el contestador no té un perfil d'agent associat, les respostes apareixeran des del perfil del bot al client.",
"TITLE": "Using Slack Integration",
"BODY": "<br/><p>Chatwoot will now sync all the incoming conversations into the <b><i>customer-conversations</i></b> channel inside your slack workplace.</p><p>Replying to a conversation thread in <b><i>customer-conversations</i></b> slack channel will create a response back to the customer through chatwoot.</p><p>Start the replies with <b><i>note:</i></b> to create private notes instead of replies.</p><p>If the replier on slack has an agent profile in chatwoot under the same email, the replies will be associated accordingly.</p><p>When the replier doesn't have an associated agent profile, the replies will be made from the bot profile.</p>",
"SELECTED": "selected"
},
"SELECT_CHANNEL": {
@@ -131,7 +131,7 @@
"KEY_PLACEHOLDER": "Enter your OpenAI API key",
"BUTTONS": {
"NEED_HELP": "Need help?",
"DISMISS": "Descartar",
"DISMISS": "Dismiss",
"FINISH": "Finish Setup"
},
"DISMISS_MESSAGE": "You can setup OpenAI integration later Whenever you want.",
@@ -46,7 +46,7 @@
"DISMISS": "Dismiss suggestion"
},
"POWERED_BY": "Chatwoot AI",
"DISMISS": "Descartar",
"DISMISS": "Dismiss",
"ADD_SELECTED_LABELS": "Add selected labels",
"ADD_SELECTED_LABEL": "Add selected label",
"ADD_ALL_LABELS": "Add all labels"
@@ -3,7 +3,7 @@
"TITLE": "Entra a Chatwoot",
"EMAIL": {
"LABEL": "Correu electrònic",
"PLACEHOLDER": "exemple@nomdelacompanyia.com",
"PLACEHOLDER": "Correu electrònic p.e.: someone@exemple.com",
"ERROR": "Introduïu una adreça de correu electrònic vàlida"
},
"PASSWORD": {
@@ -16,9 +16,9 @@
"UNAUTH": "Nom d'usuari / contrasenya incorrecte. Torna-ho a provar-ho"
},
"OAUTH": {
"GOOGLE_LOGIN": "Inicieu sessió amb Google",
"BUSINESS_ACCOUNTS_ONLY": "Si us plau, utilitzeu l'adreça de correu electrònic de la vostra empresa per iniciar sessió",
"NO_ACCOUNT_FOUND": "No hem trobat cap compte per a la vostra adreça de correu electrònic."
"GOOGLE_LOGIN": "Login with Google",
"BUSINESS_ACCOUNTS_ONLY": "Please use your company email address to login",
"NO_ACCOUNT_FOUND": "We couldn't find an account for your email address."
},
"FORGOT_PASSWORD": "Has oblidat la contrasenya?",
"CREATE_NEW_ACCOUNT": "Crear un nou compte",
@@ -92,7 +92,7 @@
"DAY": "Day",
"WEEK": "Week",
"MONTH": "Month",
"YEAR": "Any"
"YEAR": "Month"
},
"GROUP_BY_DAY_OPTIONS": [
{
@@ -140,7 +140,7 @@
],
"BUSINESS_HOURS": "Business Hours",
"FILTER_ACTIONS": {
"CLEAR_FILTER": "Esborra els filtres",
"CLEAR_FILTER": "Clear filter",
"EMPTY_LIST": "No s'ha trobat agents"
}
},
@@ -232,7 +232,7 @@
},
"FIRST_RESPONSE_TIME": {
"NAME": "First Response Time",
"DESC": "(Mitjana)",
"DESC": "( Promig )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
@@ -511,52 +511,52 @@
"SATURDAY": "Saturday"
},
"SLA_REPORTS": {
"HEADER": "Informes SLA",
"NO_RECORDS": "Les converses aplicades per l'SLA no estan disponibles.",
"LOADING": "S'estan carregant dades del SLA...",
"DOWNLOAD_SLA_REPORTS": "Descarregar informes SLA",
"DOWNLOAD_FAILED": "No s'han pogut baixar els informes SLA",
"HEADER": "SLA Reports",
"NO_RECORDS": "SLA applied conversations are not available.",
"LOADING": "Loading SLA data...",
"DOWNLOAD_SLA_REPORTS": "Download SLA reports",
"DOWNLOAD_FAILED": "Failed to download SLA Reports",
"DROPDOWN": {
"ADD_FIlTER": "Afegeix un filtre",
"CLEAR_ALL": "Esborrar tot",
"CLEAR_FILTER": "Esborra els filtres",
"ADD_FIlTER": "Add filter",
"CLEAR_ALL": "Clear all",
"CLEAR_FILTER": "Clear filter",
"EMPTY_LIST": "No s'ha trobat agents",
"NO_FILTER": "No hi ha filtres disponibles",
"SEARCH": "Cerca filtre",
"NO_FILTER": "No filters available",
"SEARCH": "Search filter",
"INPUT_PLACEHOLDER": {
"SLA": "Nom del SLA",
"AGENTS": "Nom de l'Agent",
"INBOXES": "Nom de la safata d'entrada",
"SLA": "SLA name",
"AGENTS": "Agent name",
"INBOXES": "Inbox name",
"LABELS": "Nom de l'etiqueta",
"TEAMS": "Nom de l'equip"
"TEAMS": "Team name"
},
"SLA": "Política SLA",
"INBOXES": "Safata d'entrada",
"SLA": "SLA Policy",
"INBOXES": "Inbox",
"AGENTS": "Agent",
"LABELS": "Etiqueta",
"TEAMS": "Equip"
"LABELS": "Label",
"TEAMS": "Team"
},
"METRICS": {
"HIT_RATE": {
"LABEL": "Proporció d'encerts",
"TOOLTIP": "El percentatge de SLA creats s'ha completat correctament"
"LABEL": "Hit Rate",
"TOOLTIP": "Percentage of SLAs created were completed successfully"
},
"NO_OF_MISSES": {
"LABEL": "Nombre de faltes",
"TOOLTIP": "SLA total faltes en un període determinat"
"LABEL": "Number of Misses",
"TOOLTIP": "Total SLA misses in a certain period"
},
"NO_OF_CONVERSATIONS": {
"LABEL": "Número de Converses",
"TOOLTIP": "Nombre total de converses amb SLA"
"LABEL": "Number of Conversations",
"TOOLTIP": "Total number of conversations with SLA"
}
},
"TABLE": {
"HEADER": {
"POLICY": "Política",
"CONVERSATION": "Converses",
"POLICY": "Policy",
"CONVERSATION": "Conversation",
"AGENT": "Agent"
},
"VIEW_DETAILS": "Veure Detalls"
"VIEW_DETAILS": "View Details"
}
}
}
@@ -4,12 +4,12 @@
"ALL": "Totes",
"CONTACTS": "Contactes",
"CONVERSATIONS": "Converses",
"MESSAGES": "Missatges"
"MESSAGES": "Messages"
},
"SECTION": {
"CONTACTS": "Contactes",
"CONVERSATIONS": "Converses",
"MESSAGES": "Missatges"
"MESSAGES": "Messages"
},
"EMPTY_STATE": "No %{item} found for query '%{query}'",
"EMPTY_STATE_FULL": "No results found for query '%{query}'",
@@ -91,9 +91,9 @@
"CONVERSATION_MENTION": "Enviar notificacions per mail quan siguis esmentat en una conversació",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Envia notificacions per correu electrònic quan es creï un missatge nou en una conversa assignada",
"PARTICIPATING_CONVERSATION_NEW_MESSAGE": "Send email notifications when a new message is created in a participating conversation",
"SLA_MISSED_FIRST_RESPONSE": "Envia notificacions per correu electrònic quan una conversa no compleixi un SLA de primera resposta",
"SLA_MISSED_NEXT_RESPONSE": "Envia notificacions per correu electrònic quan una conversa es perdi la següent resposta SLA",
"SLA_MISSED_RESOLUTION": "Envia notificacions per correu electrònic quan una conversa no resol un SLA"
"SLA_MISSED_FIRST_RESPONSE": "Send email notifications when a conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "Send email notifications when a conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "Send email notifications when a conversation misses resolution SLA"
},
"NOTIFICATIONS": {
"TITLE": "Notification preferences",
@@ -109,8 +109,7 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
}
},
"API": {
"UPDATE_SUCCESS": "Les teves preferències de notificació shan actualitzat correctament",
@@ -126,9 +125,9 @@
"PARTICIPATING_CONVERSATION_NEW_MESSAGE": "Send push notifications when a new message is created in a participating conversation",
"HAS_ENABLED_PUSH": "Heu activat les notificacions per a aquest navegador.",
"REQUEST_PUSH": "Activa les notificacions",
"SLA_MISSED_FIRST_RESPONSE": "Envia notificacions automàtiques quan una conversa no tingui un SLA de primera resposta",
"SLA_MISSED_NEXT_RESPONSE": "Envia notificacions automàtiques quan una conversa no s'aconsegueixi la següent resposta SLA",
"SLA_MISSED_RESOLUTION": "Envia notificacions quan una conversa no resol el SLA"
"SLA_MISSED_FIRST_RESPONSE": "Send push notifications when a conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "Send push notifications when a conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "Send push notifications when a conversation misses resolution SLA"
},
"PROFILE_IMAGE": {
"LABEL": "Imatge del Perfil"
@@ -11,7 +11,7 @@
"COMPANY_NAME": {
"LABEL": "Company name",
"PLACEHOLDER": "Enter your company name. eg: Wayne Enterprises",
"ERROR": "El nom de l'empresa és massa curt."
"ERROR": "Company name is too short"
},
"FULL_NAME": {
"LABEL": "Nom complet",
@@ -20,7 +20,7 @@
},
"EMAIL": {
"LABEL": "Email de treball",
"PLACEHOLDER": "Introdueix la teva adreça de correu electrònica de la feina. P. ex.: bruce@wayne.enterprises",
"PLACEHOLDER": "Introdueix la teva adreça email de treball. ex: bruce@wayne.enterprises",
"ERROR": "Please enter a valid work email address"
},
"PASSWORD": {
@@ -32,7 +32,7 @@
"CONFIRM_PASSWORD": {
"LABEL": "Confirma la contrasenya",
"PLACEHOLDER": "Confirma la contrasenya",
"ERROR": "La contrasenya no coincideix."
"ERROR": "Les contrasenyes no coincideixen"
},
"API": {
"SUCCESS_MESSAGE": "Registrat correctament",
@@ -1,10 +1,10 @@
{
"SLA": {
"HEADER": "SLA",
"ADD_ACTION": "Afegeix SLA",
"ADD_ACTION_LONG": "Crea una nova política de SLA",
"DESCRIPTION": "Els acords de nivell de servei (SLA) són contractes que defineixen expectatives clares entre el vostre equip i els clients. Estableixen estàndards de temps de resposta i resolució, creen un marc de responsabilitat i garanteixen una experiència coherent i d'alta qualitat.",
"LEARN_MORE": "Més informació sobre SLA",
"ADD_ACTION": "Add SLA",
"ADD_ACTION_LONG": "Create a new SLA Policy",
"DESCRIPTION": "Service Level Agreements (SLAs) are contracts that define clear expectations between your team and customers. They establish standards for response and resolution times, creating a framework for accountability and ensures a consistent, high-quality experience.",
"LEARN_MORE": "Learn more about SLA",
"LOADING": "Fetching SLAs",
"PAYWALL": {
"TITLE": "Upgrade to create SLAs",
@@ -21,10 +21,10 @@
"LIST": {
"404": "There are no SLAs available in this account.",
"EMPTY": {
"TITLE_1": "Empresa P0",
"DESC_1": "Problemes plantejats pels clients empresarials, que requereixen atenció immediata.",
"TITLE_2": "Empresa P1",
"DESC_2": "Problemes plantejats pels clients empresarials, que cal reconèixer ràpidament."
"TITLE_1": "Enterprise P0",
"DESC_1": "Issues raised by enterprise customers, that require immediate attention.",
"TITLE_2": "Enterprise P1",
"DESC_2": "Issues raised by enterprise customers, that needs to be acknowledged quickly."
},
"BUSINESS_HOURS_ON": "Business hours on",
"BUSINESS_HOURS_OFF": "Business hours off",
@@ -77,7 +77,7 @@
},
"ADD": {
"TITLE": "Add SLA",
"DESC": "Promeses amistoses per a un gran servei!",
"DESC": "Friendly promises for great service!",
"API": {
"SUCCESS_MESSAGE": "SLA added successfully",
"ERROR_MESSAGE": "S'ha produït un error; tornau-ho a provar"
@@ -97,12 +97,12 @@
}
},
"EVENTS": {
"TITLE": "Faltes SLA",
"TITLE": "SLA Misses",
"FRT": "Primer temps de resposta",
"NRT": "Pròxim temps de resposta",
"RT": "Temps de resolució",
"SHOW_MORE": "{count} més",
"HIDE": "Amaga {count} files"
"NRT": "Next response time",
"RT": "Resolution time",
"SHOW_MORE": "{count} more",
"HIDE": "Hide {count} rows"
}
}
}
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Načítání agentů",
"UPDATE": {
"CHANGE_STATUS": "Change status",
"SNOOZE_UNTIL": "Odložit",
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply.",
"UPDATE_SUCCESFUL": "Conversation status updated successfully.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -84,7 +84,6 @@
"CONFIRM": {
"TITLE": "Export Contacts",
"MESSAGE": "Are you sure you want to export all contacts?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "Yes, Export",
"NO": "No, Cancel"
}
@@ -107,7 +107,6 @@
"GENERAL": "General",
"REPORTS": "Zprávy",
"CONVERSATION": "Conversation",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "Change Assignee",
"CHANGE_PRIORITY": "Change Priority",
"CHANGE_TEAM": "Change Team",
@@ -109,8 +109,7 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
}
},
"API": {
"UPDATE_SUCCESS": "Vaše předvolby oznámení byly úspěšně aktualizovány",
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Indlæser agenter",
"UPDATE": {
"CHANGE_STATUS": "Skift status",
"SNOOZE_UNTIL": "Udsæt",
"SNOOZE_UNTIL_NEXT_REPLY": "Udsæt til næste svar.",
"UPDATE_SUCCESFUL": "Samtalens status blev opdateret.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -84,7 +84,6 @@
"CONFIRM": {
"TITLE": "Export Contacts",
"MESSAGE": "Are you sure you want to export all contacts?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "Yes, Export",
"NO": "No, Cancel"
}
@@ -107,7 +107,6 @@
"GENERAL": "Generelt",
"REPORTS": "Rapporter",
"CONVERSATION": "Samtale",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "Skift Modtager",
"CHANGE_PRIORITY": "Change Priority",
"CHANGE_TEAM": "Skift Team",
@@ -109,8 +109,7 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
}
},
"API": {
"UPDATE_SUCCESS": "Dine notifikationsindstillinger er opdateret",
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Agenten werden geladen",
"UPDATE": {
"CHANGE_STATUS": "Status ändern",
"SNOOZE_UNTIL": "Erinnern",
"SNOOZE_UNTIL_NEXT_REPLY": "Bis zur nächsten Antwort zurückstellen.",
"UPDATE_SUCCESFUL": "Der Konversationsstatus wurde erfolgreich aktualisiert.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -84,7 +84,6 @@
"CONFIRM": {
"TITLE": "Kontakte exportieren",
"MESSAGE": "Are you sure you want to export all contacts?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "Yes, Export",
"NO": "No, Cancel"
}
@@ -1,7 +1,7 @@
{
"CONVERSATION": {
"SELECT_A_CONVERSATION": "Bitte wählen Sie eine Konversation aus dem linken Bereich aus",
"CSAT_REPLY_MESSAGE": "Bitte bewerten Sie diese Konversation",
"CSAT_REPLY_MESSAGE": "Bitte bewerten Sie diese Unterhaltung",
"404": "Wir können die Konversation leider nicht finden. Bitte versuche es erneut",
"SWITCH_VIEW_LAYOUT": "Layout wechseln",
"DASHBOARD_APP_TAB_MESSAGES": "Nachrichten",
@@ -49,7 +49,7 @@
"NO_RESPONSE": "Keine Antwort",
"RATING_TITLE": "Bewertung",
"FEEDBACK_TITLE": "Feedback",
"REPLY_MESSAGE_NOT_FOUND": "Nachricht nicht verfügbar",
"REPLY_MESSAGE_NOT_FOUND": "Message not available",
"CARD": {
"SHOW_LABELS": "Labels anzeigen",
"HIDE_LABELS": "Labels ausblenden"
@@ -150,8 +150,8 @@
"SEND": "Senden",
"CREATE": "Notiz hinzufügen",
"INSERT_READ_MORE": "Mehr erfahren",
"DISMISS_REPLY": "Antwort verwerfen",
"REPLYING_TO": "Antwort auf:",
"DISMISS_REPLY": "Dismiss reply",
"REPLYING_TO": "Replying to:",
"TIP_FORMAT_ICON": "Rich Text Editor anzeigen",
"TIP_EMOJI_ICON": "Emoji-Auswahl anzeigen",
"TIP_ATTACH_ICON": "Dateien anhängen",
@@ -206,7 +206,7 @@
},
"CONTEXT_MENU": {
"COPY": "Kopieren",
"REPLY_TO": "Auf diese Nachricht antworten",
"REPLY_TO": "Reply to this message",
"DELETE": "Löschen",
"CREATE_A_CANNED_RESPONSE": "Zu vorgefertigten Antworten hinzufügen",
"TRANSLATE": "Übersetzen",
@@ -107,7 +107,6 @@
"GENERAL": "Generell",
"REPORTS": "Berichte",
"CONVERSATION": "Konversation",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "Beauftragten ändern",
"CHANGE_PRIORITY": "Priorität ändern",
"CHANGE_TEAM": "Team wechseln",
@@ -717,7 +717,7 @@
"IN_A_DAY": "Wir antworten üblicherweise innerhalb eines Tages"
},
"FOOTER": {
"START_CONVERSATION_BUTTON_TEXT": "Konversation beginnen",
"START_CONVERSATION_BUTTON_TEXT": "Unterhaltung beginnen",
"CHAT_INPUT_PLACEHOLDER": "Schreiben Sie Ihre Nachricht"
},
"BODY": {
@@ -1,7 +1,7 @@
{
"RESET_PASSWORD": {
"TITLE": "Passwort zurücksetzen",
"DESCRIPTION": "Geben Sie die E-Mail-Adresse ein, mit der Sie sich bei Chatwoot anmelden, um die Anweisungen zum Zurücksetzen des Passworts zu erhalten.",
"DESCRIPTION": "Enter the email address you use to log in to Chatwoot to get the password reset instructions.",
"GO_BACK_TO_LOGIN": "If you want to go back to the login page,",
"EMAIL": {
"LABEL": "E-Mail",
@@ -109,8 +109,7 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
}
},
"API": {
"UPDATE_SUCCESS": "Ihre Benachrichtigungseinstellungen wurden erfolgreich aktualisiert",
@@ -190,9 +189,9 @@
"TRIAL_MESSAGE": "Tage der Testversion verbleibend.",
"TRAIL_BUTTON": "Jetzt kaufen",
"DELETED_USER": "Gelöschter Benutzer",
"EMAIL_VERIFICATION_PENDING": "Es scheint, dass Sie Ihre E-Mail-Adresse noch nicht verifiziert haben. Bitte überprüfen Sie Ihren Posteingang auf die Verifizierungs-E-Mail.",
"RESEND_VERIFICATION_MAIL": "Verifizierungs-E-Mail erneut senden",
"EMAIL_VERIFICATION_SENT": "Verifizierungs-E-Mail wurde versendet. Bitte überprüfen Sie Ihren Posteingang.",
"EMAIL_VERIFICATION_PENDING": "It seems that you haven't verified your email address yet. Please check your inbox for the verification email.",
"RESEND_VERIFICATION_MAIL": "Resend verification email",
"EMAIL_VERIFICATION_SENT": "Verification email has been sent. Please check your inbox.",
"ACCOUNT_SUSPENDED": {
"TITLE": "Konto gesperrt",
"MESSAGE": "Ihr Account wurde gesperrt. Wenden Sie sich für weitere Informationen bitte an das Support-Team."
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Φόρτωση πρακτόρων",
"UPDATE": {
"CHANGE_STATUS": "Αλλαγή κατάστασης",
"SNOOZE_UNTIL": "Αναβολή",
"SNOOZE_UNTIL_NEXT_REPLY": "Αναβολή έως την επόμενη απάντηση.",
"UPDATE_SUCCESFUL": "Η κατάσταση συνομιλίας ενημερώθηκε με επιτυχία.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -84,7 +84,6 @@
"CONFIRM": {
"TITLE": "Export Contacts",
"MESSAGE": "Are you sure you want to export all contacts?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "Yes, Export",
"NO": "No, Cancel"
}
@@ -107,7 +107,6 @@
"GENERAL": "Γενικά",
"REPORTS": "Αναφορές",
"CONVERSATION": "Συνομιλία",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "Αλλαγή Αναδόχου",
"CHANGE_PRIORITY": "Change Priority",
"CHANGE_TEAM": "Αλλαγή Ομάδας",
@@ -109,8 +109,7 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
}
},
"API": {
"UPDATE_SUCCESS": "Οι προτιμήσεις σας για τις ειδοποιήσεις ενημερώθηκαν",
@@ -154,7 +154,7 @@
"UNTIL_TOMORROW": "Until tomorrow",
"UNTIL_NEXT_MONTH": "Until next month",
"AN_HOUR_FROM_NOW": "Until an hour from now",
"UNTIL_CUSTOM_TIME": "Custom...",
"CUSTOM": "Custom...",
"CHANGE_APPEARANCE": "Change Appearance",
"LIGHT_MODE": "Light",
"DARK_MODE": "Dark",
@@ -8,8 +8,9 @@
"NO_NOTIFICATIONS": "No notifications",
"NOTE": "Notifications from all subscribed inboxes",
"NO_MESSAGES_AVAILABLE": "Oops! Not able to fetch messages",
"SNOOZED_UNTIL": "Well remind you after {time}",
"SNOOZED_ENDS": "We wont notify you again"
"SNOOZED_UNTIL": "Snoozed until",
"SNOOZED_UNTIL_TOMORROW": "Snoozed until tomorrow",
"SNOOZED_UNTIL_NEXT_WEEK": "Snoozed until next week"
},
"ACTION_HEADER": {
"SNOOZE": "Snooze notification",
@@ -17,11 +18,14 @@
"BACK": "Back"
},
"TYPES": {
"CONVERSATION_MENTION": "Mention",
"CONVERSATION_ASSIGNMENT": "Assigned to you",
"SLA_MISSED_FIRST_RESPONSE": "SLA breach",
"SLA_MISSED_NEXT_RESPONSE": "SLA breach",
"SLA_MISSED_RESOLUTION": "SLA breach"
"CONVERSATION_MENTION": "You have been mentioned in a conversation",
"CONVERSATION_CREATION": "New conversation created",
"CONVERSATION_ASSIGNMENT": "A conversation has been assigned to you",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "New message in an assigned conversation",
"PARTICIPATING_CONVERSATION_NEW_MESSAGE": "New message in a conversation you are participating in",
"SLA_MISSED_FIRST_RESPONSE": "SLA target first response missed for conversation",
"SLA_MISSED_NEXT_RESPONSE": "SLA target next response missed for conversation",
"SLA_MISSED_RESOLUTION": "SLA target resolution missed for conversation"
},
"MENU_ITEM": {
"MARK_AS_READ": "Mark as read",
@@ -2,7 +2,6 @@
"INBOX_MGMT": {
"HEADER": "Inboxes",
"SIDEBAR_TXT": "<p><b>Inbox</b></p> <p> When you connect a website or a facebook Page to Chatwoot, it is called an <b>Inbox</b>. You can have unlimited inboxes in your Chatwoot account. </p><p> Click on <b>Add Inbox</b> to connect a website or a Facebook Page. </p><p> In the Dashboard, you can see all the conversations from all your inboxes in a single place and respond to them under the `Conversations` tab. </p><p> You can also see conversations specific to an inbox by clicking on the inbox name on the left pane of the dashboard. </p>",
"RECONNECTION_REQUIRED": "Your inbox is disconnected, you will not receive any new messages. <a class=\"underline\" href=\"%{actionUrl}\">Click here</a> to reconnect.",
"LIST": {
"404": "There are no inboxes attached to this account."
},
@@ -737,4 +736,4 @@
"OTHER_PROVIDERS": "Other Providers"
}
}
}
}
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Cargando agentes",
"UPDATE": {
"CHANGE_STATUS": "Cambiar estado",
"SNOOZE_UNTIL": "Posponer",
"SNOOZE_UNTIL_NEXT_REPLY": "Posponer hasta la próxima respuesta.",
"UPDATE_SUCCESFUL": "El estado de la conversación se actualizó con éxito.",
"UPDATE_FAILED": "No se han podido actualizar las conversaciones. Inténtalo de nuevo."
},
@@ -84,7 +84,6 @@
"CONFIRM": {
"TITLE": "Exportar contactos",
"MESSAGE": "¿Está seguro que desea exportar todos los contactos?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "Sí, exportar",
"NO": "No, cancelar"
}
@@ -107,7 +107,6 @@
"GENERAL": "General",
"REPORTS": "Informes",
"CONVERSATION": "Conversación",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "Cambiar Asignado",
"CHANGE_PRIORITY": "Cambiar prioridad",
"CHANGE_TEAM": "Cambiar equipo",
@@ -109,8 +109,7 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
}
},
"API": {
"UPDATE_SUCCESS": "Sus preferencias de notificación se actualizaron correctamente",
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "بارگذاری ایجنت ها",
"UPDATE": {
"CHANGE_STATUS": "تغییر وضعیت",
"SNOOZE_UNTIL": "به تعویق انداختن",
"SNOOZE_UNTIL_NEXT_REPLY": "تا پاسخ بعدی به تعویق بی‌افتد.",
"UPDATE_SUCCESFUL": "وضعیت گفتگو با موفقیت به روز شد.",
"UPDATE_FAILED": "گفتگوها به روز نشدند، لطفا دوباره امتحان کنید."
},
@@ -84,7 +84,6 @@
"CONFIRM": {
"TITLE": "خروجی گرفتن مخاطب ها",
"MESSAGE": "آیا مطمئن هستید که می خواهید از همه مخاطبین خروجی بگیرید؟",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "بله، خروجی بگیر",
"NO": "نه، انصراف"
}
@@ -1,13 +1,13 @@
{
"DATE_PICKER": {
"APPLY_BUTTON": "درخواست دادن",
"CLEAR_BUTTON": "پاک کردن",
"CLEAR_BUTTON": "Clear",
"DATE_RANGE_INPUT": {
"START": "تاریخ شروع",
"END": "تاریخ پایان"
"START": "Start Date",
"END": "End Date"
},
"DATE_RANGE_OPTIONS": {
"TITLE": "محدوده زمانی",
"TITLE": "DATE RANGE",
"LAST_7_DAYS": "در ۷ روز گذشته",
"LAST_30_DAYS": "در ۳۰ روز گذشته",
"LAST_3_MONTHS": "۳ ماه گذشته",
@@ -107,7 +107,6 @@
"GENERAL": "عمومی",
"REPORTS": "گزارشات",
"CONVERSATION": "گفتگو",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "اپراتور را تغییر دهید",
"CHANGE_PRIORITY": "تغییر اولویت",
"CHANGE_TEAM": "تیم را تغییر دهید",
@@ -81,12 +81,12 @@
"HELP_TEXT": {
"TITLE": "استفاده از اسلک",
"BODY": "<br/> <p> ما اکنون تمام مکالمات ورودی را در کانال <b><i> گفتگوهای مشتری </i> </b> داخل محل کار شما همگام سازی می کنیم. </p> <p> پاسخ به یک رشته مکالمه در کانال مکالمه <b><i> مشتری- مکالمات </ i> </b> از طریق برنامه پاسخی به مشتری ایجاد می کند. </ p> <p> پاسخ ها را با <b> <i> شروع کنید توجه: </i> </b> برای ایجاد یادداشت های خصوصی به جای پاسخ ها. </p> <p> اگر پاسخ دهنده در slack نمایه نماینده ای در برنامه تحت همان ایمیل داشته باشد ، پاسخ ها به همین ترتیب مرتبط می شوند. </ p> <p> وقتی ارسال کننده نمایه نماینده مرتبطی ندارد ، پاسخها از نمایه ربات انجام می شود. </p>",
"SELECTED": "انتخاب شد"
"SELECTED": "selected"
},
"SELECT_CHANNEL": {
"OPTION_LABEL": "یک کانال انتخاب کنید",
"OPTION_LABEL": "Select a channel",
"UPDATE": "اعمال شود",
"BUTTON_TEXT": "اتصال کانال",
"BUTTON_TEXT": "Connect channel",
"DESCRIPTION": "Your Slack workspace is now linked with Chatwoot. However, the integration is currently inactive. To activate the integration and connect a channel to Chatwoot, please click the button below.\n\n**Note:** If you are attempting to connect a private channel, add the Chatwoot app to the Slack channel before proceeding with this step.",
"ATTENTION_REQUIRED": "Attention required",
"EXPIRED": "Your Slack integration has expired. To continue receiving messages on Slack, please delete the integration and connect your workspace again."
@@ -106,7 +106,7 @@
"AI_ASSIST": "دستیار هوش مصنوعی",
"WITH_AI": " %{option} with AI ",
"OPTIONS": {
"REPLY_SUGGESTION": "پیشنهاد پاسخ",
"REPLY_SUGGESTION": "Reply Suggestion",
"SUMMARIZE": "Summarize",
"REPHRASE": "Improve Writing",
"FIX_SPELLING_GRAMMAR": "Fix Spelling and Grammar",
@@ -144,13 +144,13 @@
"TONE": {
"TITLE": "Tone",
"OPTIONS": {
"PROFESSIONAL": "حرفه‌ای",
"PROFESSIONAL": "Professional",
"FRIENDLY": "دوستانه"
}
},
"BUTTONS": {
"GENERATE": "تولید",
"GENERATING": "در حال تولید...",
"GENERATE": "Generate",
"GENERATING": "Generating...",
"CANCEL": "انصراف"
},
"GENERATE_ERROR": "There was an error processing the content, please try again"
@@ -10,7 +10,7 @@
"PASSWORD_UPDATE_SUCCESS": "رمز عبورتان عوض شد",
"AFTER_EMAIL_CHANGED": "پروفایلتان با موفقیت تغییر یافت، اطلاعات ورود به سیستم تغییر کرده لذا لطفا مجددا به سیستم وارد شوید",
"FORM": {
"PICTURE": "عکس نمایه",
"PICTURE": "Profile Picture",
"AVATAR": "عکس پروفایل",
"ERROR": "لطفا ایرادات ذکر شده را برطرف کنید",
"REMOVE_IMAGE": "حذف",
@@ -65,7 +65,7 @@
"NOTE": "اعلان های صوتی را در داشبورد برای پیام ها و مکالمات جدید فعال کنید.",
"ALERT_TYPES": {
"NONE": "هیچکدام",
"MINE": "اختصاص داده",
"MINE": "Assigned",
"ALL": "همه"
},
"ALERT_TYPE": {
@@ -96,8 +96,8 @@
"SLA_MISSED_RESOLUTION": "هر وقت گفتگویی سیاست SLA مربوط به زمان حل موضوع را نقض کرد ایمیل ارسال کن"
},
"NOTIFICATIONS": {
"TITLE": "تنظیمات آگاه‌سازی",
"TYPE_TITLE": "نوع آگاه‌سازی",
"TITLE": "Notification preferences",
"TYPE_TITLE": "Notification type",
"EMAIL": "ایمیل",
"PUSH": "Push notification",
"TYPES": {
@@ -109,8 +109,7 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
}
},
"API": {
"UPDATE_SUCCESS": "تغییرات تنظیمات اعلامیه‌ها با موفقیت ثبت شد",
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Loading agents",
"UPDATE": {
"CHANGE_STATUS": "Change status",
"SNOOZE_UNTIL": "Snooze",
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply.",
"UPDATE_SUCCESFUL": "Conversation status updated successfully.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -84,7 +84,6 @@
"CONFIRM": {
"TITLE": "Export Contacts",
"MESSAGE": "Are you sure you want to export all contacts?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "Yes, Export",
"NO": "No, Cancel"
}

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