Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f2e5abdda |
@@ -16,7 +16,7 @@ on:
|
||||
|
||||
jobs:
|
||||
nightly:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
|
||||
- name: get installer
|
||||
|
||||
+2
-2
@@ -561,7 +561,7 @@ GEM
|
||||
activesupport (>= 3.0.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.8.1)
|
||||
rack (2.2.12)
|
||||
rack (2.2.11)
|
||||
rack-attack (6.7.0)
|
||||
rack (>= 1.0, < 4)
|
||||
rack-contrib (2.5.0)
|
||||
@@ -799,7 +799,7 @@ GEM
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (2.4.2)
|
||||
uniform_notifier (1.16.0)
|
||||
uri (1.0.3)
|
||||
uri (0.13.0)
|
||||
uri_template (0.7.0)
|
||||
valid_email2 (5.2.6)
|
||||
activemodel (>= 3.2)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
button:not(.reset-base),
|
||||
input[type='button']:not(.reset-base),
|
||||
input[type='reset']:not(.reset-base),
|
||||
input[type='submit']:not(.reset-base),
|
||||
.button:not(.reset-base) {
|
||||
button,
|
||||
input[type="button"],
|
||||
input[type="reset"],
|
||||
input[type="submit"],
|
||||
.button {
|
||||
appearance: none;
|
||||
background-color: $color-woot;
|
||||
border: 0;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
.icon-container {
|
||||
margin-right: 2px;
|
||||
|
||||
}
|
||||
|
||||
.value-container {
|
||||
|
||||
@@ -6,8 +6,6 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
before_action :conversation, except: [:index, :meta, :search, :create, :filter]
|
||||
before_action :inbox, :contact, :contact_inbox, only: [:create]
|
||||
|
||||
ATTACHMENT_RESULTS_PER_PAGE = 100
|
||||
|
||||
def index
|
||||
result = conversation_finder.perform
|
||||
@conversations = result[:conversations]
|
||||
@@ -26,12 +24,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
end
|
||||
|
||||
def attachments
|
||||
@attachments_count = @conversation.attachments.count
|
||||
@attachments = @conversation.attachments
|
||||
.includes(:message)
|
||||
.order(created_at: :desc)
|
||||
.page(attachment_params[:page])
|
||||
.per(ATTACHMENT_RESULTS_PER_PAGE)
|
||||
end
|
||||
|
||||
def show; end
|
||||
@@ -131,10 +124,6 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
params.permit(:priority)
|
||||
end
|
||||
|
||||
def attachment_params
|
||||
params.permit(:page)
|
||||
end
|
||||
|
||||
def update_last_seen_on_conversation(last_seen_at, update_assignee)
|
||||
# rubocop:disable Rails/SkipsModelValidations
|
||||
@conversation.update_column(:agent_last_seen_at, last_seen_at)
|
||||
|
||||
@@ -5,11 +5,10 @@ module SwitchLocale
|
||||
|
||||
def switch_locale(&)
|
||||
# priority is for locale set in query string (mostly for widget/from js sdk)
|
||||
locale ||= params[:locale]
|
||||
|
||||
locale ||= locale_from_params
|
||||
locale ||= locale_from_custom_domain
|
||||
# if locale is not set in account, let's use DEFAULT_LOCALE env variable
|
||||
locale ||= ENV.fetch('DEFAULT_LOCALE', nil)
|
||||
locale ||= locale_from_env_variable
|
||||
set_locale(locale, &)
|
||||
end
|
||||
|
||||
@@ -33,30 +32,26 @@ module SwitchLocale
|
||||
end
|
||||
|
||||
def set_locale(locale, &)
|
||||
safe_locale = validate_and_get_locale(locale)
|
||||
# if locale is empty, use default_locale
|
||||
locale ||= I18n.default_locale
|
||||
# Ensure locale won't bleed into other requests
|
||||
# https://guides.rubyonrails.org/i18n.html#managing-the-locale-across-requests
|
||||
I18n.with_locale(safe_locale, &)
|
||||
I18n.with_locale(locale, &)
|
||||
end
|
||||
|
||||
def validate_and_get_locale(locale)
|
||||
return I18n.default_locale.to_s if locale.blank?
|
||||
|
||||
available_locales = I18n.available_locales.map(&:to_s)
|
||||
locale_without_variant = locale.split('_')[0]
|
||||
|
||||
if available_locales.include?(locale)
|
||||
locale
|
||||
elsif available_locales.include?(locale_without_variant)
|
||||
locale_without_variant
|
||||
else
|
||||
I18n.default_locale.to_s
|
||||
end
|
||||
def locale_from_params
|
||||
I18n.available_locales.map(&:to_s).include?(params[:locale]) ? params[:locale] : nil
|
||||
end
|
||||
|
||||
def locale_from_account(account)
|
||||
return unless account
|
||||
|
||||
account.locale
|
||||
I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil
|
||||
end
|
||||
|
||||
def locale_from_env_variable
|
||||
return unless ENV.fetch('DEFAULT_LOCALE', nil)
|
||||
|
||||
I18n.available_locales.map(&:to_s).include?(ENV.fetch('DEFAULT_LOCALE')) ? ENV.fetch('DEFAULT_LOCALE') : nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
class Public::Api::V1::Portals::BaseController < PublicController
|
||||
include SwitchLocale
|
||||
|
||||
before_action :show_plain_layout
|
||||
before_action :set_color_scheme
|
||||
before_action :set_global_config
|
||||
@@ -29,7 +27,14 @@ class Public::Api::V1::Portals::BaseController < PublicController
|
||||
end
|
||||
|
||||
def switch_locale_with_portal(&)
|
||||
@locale = validate_and_get_locale(params[:locale])
|
||||
locale_without_variant = params[:locale].split('_')[0]
|
||||
is_locale_available = I18n.available_locales.map(&:to_s).include?(params[:locale])
|
||||
is_locale_variant_available = I18n.available_locales.map(&:to_s).include?(locale_without_variant)
|
||||
if is_locale_available
|
||||
@locale = params[:locale]
|
||||
elsif is_locale_variant_available
|
||||
@locale = locale_without_variant
|
||||
end
|
||||
|
||||
I18n.with_locale(@locale, &)
|
||||
end
|
||||
@@ -39,12 +44,12 @@ class Public::Api::V1::Portals::BaseController < PublicController
|
||||
Rails.logger.info "Article: not found for slug: #{params[:article_slug]}"
|
||||
render_404 && return if article.blank?
|
||||
|
||||
article_locale = if article.category.present?
|
||||
article.category.locale
|
||||
else
|
||||
article.portal.default_locale
|
||||
end
|
||||
@locale = validate_and_get_locale(article_locale)
|
||||
@locale = if article.category.present?
|
||||
article.category.locale
|
||||
else
|
||||
article.portal.default_locale
|
||||
end
|
||||
|
||||
I18n.with_locale(@locale, &)
|
||||
end
|
||||
|
||||
|
||||
@@ -78,11 +78,7 @@ class AccountDashboard < Administrate::BaseDashboard
|
||||
# COLLECTION_FILTERS = {
|
||||
# open: ->(resources) { resources.where(open: true) }
|
||||
# }.freeze
|
||||
COLLECTION_FILTERS = {
|
||||
active: ->(resources) { resources.where(status: :active) },
|
||||
suspended: ->(resources) { resources.where(status: :suspended) },
|
||||
recent: ->(resources) { resources.where('created_at > ?', 30.days.ago) }
|
||||
}.freeze
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
|
||||
# Overwrite this method to customize how accounts are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
|
||||
@@ -94,12 +94,7 @@ class UserDashboard < Administrate::BaseDashboard
|
||||
# COLLECTION_FILTERS = {
|
||||
# open: ->(resources) { resources.where(open: true) }
|
||||
# }.freeze
|
||||
COLLECTION_FILTERS = {
|
||||
super_admin: ->(resources) { resources.where(type: 'SuperAdmin') },
|
||||
confirmed: ->(resources) { resources.where.not(confirmed_at: nil) },
|
||||
unconfirmed: ->(resources) { resources.where(confirmed_at: nil) },
|
||||
recent: ->(resources) { resources.where('created_at > ?', 30.days.ago) }
|
||||
}.freeze
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
|
||||
# Overwrite this method to customize how users are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module Filters::FilterHelper
|
||||
module FilterHelper
|
||||
def build_condition_query(model_filters, query_hash, current_index)
|
||||
current_filter = model_filters[query_hash['attribute_key']]
|
||||
|
||||
@@ -89,18 +89,4 @@ module Filters::FilterHelper
|
||||
operator = condition['query_operator'].upcase
|
||||
raise CustomExceptions::CustomFilter::InvalidQueryOperator.new({}) unless %w[AND OR].include?(operator)
|
||||
end
|
||||
|
||||
def conversation_status_values(values)
|
||||
return Conversation.statuses.values if values.include?('all')
|
||||
|
||||
values.map { |x| Conversation.statuses[x.to_sym] }
|
||||
end
|
||||
|
||||
def conversation_priority_values(values)
|
||||
values.map { |x| Conversation.priorities[x.to_sym] }
|
||||
end
|
||||
|
||||
def message_type_values(values)
|
||||
values.map { |x| Message.message_types[x.to_sym] }
|
||||
end
|
||||
end
|
||||
@@ -2,10 +2,7 @@ import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useOperators } from './operators';
|
||||
import { useMapGetter } from 'dashboard/composables/store.js';
|
||||
import {
|
||||
buildAttributesFilterTypes,
|
||||
CONTACT_ATTRIBUTES,
|
||||
} from './helper/filterHelper.js';
|
||||
import { buildAttributesFilterTypes } from './helper/filterHelper.js';
|
||||
import countries from 'shared/constants/countries.js';
|
||||
|
||||
/**
|
||||
@@ -62,11 +59,7 @@ export function useContactFilterContext() {
|
||||
* @type {import('vue').ComputedRef<FilterType[]>}
|
||||
*/
|
||||
const customFilterTypes = computed(() =>
|
||||
buildAttributesFilterTypes(
|
||||
contactAttributes.value,
|
||||
getOperatorTypes,
|
||||
'contact'
|
||||
)
|
||||
buildAttributesFilterTypes(contactAttributes.value, getOperatorTypes)
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -74,8 +67,8 @@ export function useContactFilterContext() {
|
||||
*/
|
||||
const filterTypes = computed(() => [
|
||||
{
|
||||
attributeKey: CONTACT_ATTRIBUTES.NAME,
|
||||
value: CONTACT_ATTRIBUTES.NAME,
|
||||
attributeKey: 'name',
|
||||
value: 'name',
|
||||
attributeName: t('CONTACTS_LAYOUT.FILTER.NAME'),
|
||||
label: t('CONTACTS_LAYOUT.FILTER.NAME'),
|
||||
inputType: 'plainText',
|
||||
@@ -84,8 +77,8 @@ export function useContactFilterContext() {
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONTACT_ATTRIBUTES.EMAIL,
|
||||
value: CONTACT_ATTRIBUTES.EMAIL,
|
||||
attributeKey: 'email',
|
||||
value: 'email',
|
||||
attributeName: t('CONTACTS_LAYOUT.FILTER.EMAIL'),
|
||||
label: t('CONTACTS_LAYOUT.FILTER.EMAIL'),
|
||||
inputType: 'plainText',
|
||||
@@ -94,8 +87,8 @@ export function useContactFilterContext() {
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONTACT_ATTRIBUTES.PHONE_NUMBER,
|
||||
value: CONTACT_ATTRIBUTES.PHONE_NUMBER,
|
||||
attributeKey: 'phone_number',
|
||||
value: 'phone_number',
|
||||
attributeName: t('CONTACTS_LAYOUT.FILTER.PHONE_NUMBER'),
|
||||
label: t('CONTACTS_LAYOUT.FILTER.PHONE_NUMBER'),
|
||||
inputType: 'plainText',
|
||||
@@ -104,8 +97,8 @@ export function useContactFilterContext() {
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONTACT_ATTRIBUTES.IDENTIFIER,
|
||||
value: CONTACT_ATTRIBUTES.IDENTIFIER,
|
||||
attributeKey: 'identifier',
|
||||
value: 'identifier',
|
||||
attributeName: t('CONTACTS_LAYOUT.FILTER.IDENTIFIER'),
|
||||
label: t('CONTACTS_LAYOUT.FILTER.IDENTIFIER'),
|
||||
inputType: 'plainText',
|
||||
@@ -114,8 +107,8 @@ export function useContactFilterContext() {
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONTACT_ATTRIBUTES.COUNTRY_CODE,
|
||||
value: CONTACT_ATTRIBUTES.COUNTRY_CODE,
|
||||
attributeKey: 'country_code',
|
||||
value: 'country_code',
|
||||
attributeName: t('FILTER.ATTRIBUTES.COUNTRY_NAME'),
|
||||
label: t('FILTER.ATTRIBUTES.COUNTRY_NAME'),
|
||||
inputType: 'searchSelect',
|
||||
@@ -125,8 +118,8 @@ export function useContactFilterContext() {
|
||||
attributeModel: 'additional',
|
||||
},
|
||||
{
|
||||
attributeKey: CONTACT_ATTRIBUTES.CITY,
|
||||
value: CONTACT_ATTRIBUTES.CITY,
|
||||
attributeKey: 'city',
|
||||
value: 'city',
|
||||
attributeName: t('CONTACTS_LAYOUT.FILTER.CITY'),
|
||||
label: t('CONTACTS_LAYOUT.FILTER.CITY'),
|
||||
inputType: 'plainText',
|
||||
@@ -135,8 +128,8 @@ export function useContactFilterContext() {
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONTACT_ATTRIBUTES.CREATED_AT,
|
||||
value: CONTACT_ATTRIBUTES.CREATED_AT,
|
||||
attributeKey: 'created_at',
|
||||
value: 'created_at',
|
||||
attributeName: t('CONTACTS_LAYOUT.FILTER.CREATED_AT'),
|
||||
label: t('CONTACTS_LAYOUT.FILTER.CREATED_AT'),
|
||||
inputType: 'date',
|
||||
@@ -145,8 +138,8 @@ export function useContactFilterContext() {
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONTACT_ATTRIBUTES.LAST_ACTIVITY_AT,
|
||||
value: CONTACT_ATTRIBUTES.LAST_ACTIVITY_AT,
|
||||
attributeKey: 'last_activity_at',
|
||||
value: 'last_activity_at',
|
||||
attributeName: t('CONTACTS_LAYOUT.FILTER.LAST_ACTIVITY'),
|
||||
label: t('CONTACTS_LAYOUT.FILTER.LAST_ACTIVITY'),
|
||||
inputType: 'date',
|
||||
@@ -155,8 +148,8 @@ export function useContactFilterContext() {
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONTACT_ATTRIBUTES.REFERER,
|
||||
value: CONTACT_ATTRIBUTES.REFERER,
|
||||
attributeKey: 'referer',
|
||||
value: 'referer',
|
||||
attributeName: t('CONTACTS_LAYOUT.FILTER.REFERER_LINK'),
|
||||
label: t('CONTACTS_LAYOUT.FILTER.REFERER_LINK'),
|
||||
inputType: 'plainText',
|
||||
@@ -165,8 +158,8 @@ export function useContactFilterContext() {
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONTACT_ATTRIBUTES.BLOCKED,
|
||||
value: CONTACT_ATTRIBUTES.BLOCKED,
|
||||
attributeKey: 'blocked',
|
||||
value: 'blocked',
|
||||
attributeName: t('CONTACTS_LAYOUT.FILTER.BLOCKED'),
|
||||
label: t('CONTACTS_LAYOUT.FILTER.BLOCKED'),
|
||||
inputType: 'searchSelect',
|
||||
|
||||
@@ -1,35 +1,3 @@
|
||||
/**
|
||||
* Standard attributes of the conversation model
|
||||
*/
|
||||
export const CONVERSATION_ATTRIBUTES = {
|
||||
STATUS: 'status',
|
||||
PRIORITY: 'priority',
|
||||
ASSIGNEE_ID: 'assignee_id',
|
||||
INBOX_ID: 'inbox_id',
|
||||
TEAM_ID: 'team_id',
|
||||
DISPLAY_ID: 'display_id',
|
||||
CAMPAIGN_ID: 'campaign_id',
|
||||
LABELS: 'labels',
|
||||
BROWSER_LANGUAGE: 'browser_language',
|
||||
COUNTRY_CODE: 'country_code',
|
||||
REFERER: 'referer',
|
||||
CREATED_AT: 'created_at',
|
||||
LAST_ACTIVITY_AT: 'last_activity_at',
|
||||
};
|
||||
|
||||
export const CONTACT_ATTRIBUTES = {
|
||||
NAME: 'name',
|
||||
EMAIL: 'email',
|
||||
PHONE_NUMBER: 'phone_number',
|
||||
IDENTIFIER: 'identifier',
|
||||
COUNTRY_CODE: 'country_code',
|
||||
CITY: 'city',
|
||||
CREATED_AT: 'created_at',
|
||||
LAST_ACTIVITY_AT: 'last_activity_at',
|
||||
REFERER: 'referer',
|
||||
BLOCKED: 'blocked',
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines the input type for a custom attribute based on its key
|
||||
* @param {string} key - The attribute display type key
|
||||
@@ -52,37 +20,24 @@ export const getCustomAttributeInputType = key => {
|
||||
|
||||
/**
|
||||
* Builds filter types for custom attributes
|
||||
* This also removes any conflicting attributes
|
||||
* @param {Array} attributes - The attributes array
|
||||
* @param {Function} getOperatorTypes - Function to get operator types
|
||||
* @returns {Array} Array of filter types
|
||||
*/
|
||||
export const buildAttributesFilterTypes = (
|
||||
attributes,
|
||||
getOperatorTypes,
|
||||
filterModel = 'conversation'
|
||||
) => {
|
||||
const standardAttributes = Object.values(
|
||||
filterModel === 'conversation'
|
||||
? CONVERSATION_ATTRIBUTES
|
||||
: CONTACT_ATTRIBUTES
|
||||
);
|
||||
|
||||
return attributes
|
||||
.filter(attr => !standardAttributes.includes(attr.attributeKey))
|
||||
.map(attr => ({
|
||||
attributeKey: attr.attributeKey,
|
||||
value: attr.attributeKey,
|
||||
attributeName: attr.attributeDisplayName,
|
||||
label: attr.attributeDisplayName,
|
||||
inputType: getCustomAttributeInputType(attr.attributeDisplayType),
|
||||
filterOperators: getOperatorTypes(attr.attributeDisplayType),
|
||||
options:
|
||||
attr.attributeDisplayType === 'list'
|
||||
? attr.attributeValues.map(item => ({ id: item, name: item }))
|
||||
: [],
|
||||
attributeModel: 'customAttributes',
|
||||
}));
|
||||
export const buildAttributesFilterTypes = (attributes, getOperatorTypes) => {
|
||||
return attributes.map(attr => ({
|
||||
attributeKey: attr.attributeKey,
|
||||
value: attr.attributeKey,
|
||||
attributeName: attr.attributeDisplayName,
|
||||
label: attr.attributeDisplayName,
|
||||
inputType: getCustomAttributeInputType(attr.attributeDisplayType),
|
||||
filterOperators: getOperatorTypes(attr.attributeDisplayType),
|
||||
options:
|
||||
attr.attributeDisplayType === 'list'
|
||||
? attr.attributeValues.map(item => ({ id: item, name: item }))
|
||||
: [],
|
||||
attributeModel: 'customAttributes',
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,10 +3,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import { useOperators } from './operators';
|
||||
import { useMapGetter } from 'dashboard/composables/store.js';
|
||||
import { useChannelIcon } from 'next/icon/provider';
|
||||
import {
|
||||
buildAttributesFilterTypes,
|
||||
CONVERSATION_ATTRIBUTES,
|
||||
} from './helper/filterHelper';
|
||||
import { buildAttributesFilterTypes } from './helper/filterHelper';
|
||||
import countries from 'shared/constants/countries.js';
|
||||
import languages from 'dashboard/components/widgets/conversation/advancedFilterItems/languages.js';
|
||||
|
||||
@@ -73,11 +70,7 @@ export function useConversationFilterContext() {
|
||||
* @type {import('vue').ComputedRef<FilterType[]>}
|
||||
*/
|
||||
const customFilterTypes = computed(() =>
|
||||
buildAttributesFilterTypes(
|
||||
conversationAttributes.value,
|
||||
getOperatorTypes,
|
||||
'conversation'
|
||||
)
|
||||
buildAttributesFilterTypes(conversationAttributes.value, getOperatorTypes)
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -85,8 +78,8 @@ export function useConversationFilterContext() {
|
||||
*/
|
||||
const filterTypes = computed(() => [
|
||||
{
|
||||
attributeKey: CONVERSATION_ATTRIBUTES.STATUS,
|
||||
value: CONVERSATION_ATTRIBUTES.STATUS,
|
||||
attributeKey: 'status',
|
||||
value: 'status',
|
||||
attributeName: t('FILTER.ATTRIBUTES.STATUS'),
|
||||
label: t('FILTER.ATTRIBUTES.STATUS'),
|
||||
inputType: 'multiSelect',
|
||||
@@ -101,24 +94,8 @@ export function useConversationFilterContext() {
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONVERSATION_ATTRIBUTES.PRIORITY,
|
||||
value: CONVERSATION_ATTRIBUTES.PRIORITY,
|
||||
attributeName: t('FILTER.ATTRIBUTES.PRIORITY'),
|
||||
label: t('FILTER.ATTRIBUTES.PRIORITY'),
|
||||
inputType: 'multiSelect',
|
||||
options: ['low', 'medium', 'high', 'urgent'].map(id => {
|
||||
return {
|
||||
id,
|
||||
name: t(`CONVERSATION.PRIORITY.OPTIONS.${id.toUpperCase()}`),
|
||||
};
|
||||
}),
|
||||
dataType: 'text',
|
||||
filterOperators: equalityOperators.value,
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONVERSATION_ATTRIBUTES.ASSIGNEE_ID,
|
||||
value: CONVERSATION_ATTRIBUTES.ASSIGNEE_ID,
|
||||
attributeKey: 'assignee_id',
|
||||
value: 'assignee_id',
|
||||
attributeName: t('FILTER.ATTRIBUTES.ASSIGNEE_NAME'),
|
||||
label: t('FILTER.ATTRIBUTES.ASSIGNEE_NAME'),
|
||||
inputType: 'searchSelect',
|
||||
@@ -133,8 +110,8 @@ export function useConversationFilterContext() {
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONVERSATION_ATTRIBUTES.INBOX_ID,
|
||||
value: CONVERSATION_ATTRIBUTES.INBOX_ID,
|
||||
attributeKey: 'inbox_id',
|
||||
value: 'inbox_id',
|
||||
attributeName: t('FILTER.ATTRIBUTES.INBOX_NAME'),
|
||||
label: t('FILTER.ATTRIBUTES.INBOX_NAME'),
|
||||
inputType: 'searchSelect',
|
||||
@@ -149,8 +126,8 @@ export function useConversationFilterContext() {
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONVERSATION_ATTRIBUTES.TEAM_ID,
|
||||
value: CONVERSATION_ATTRIBUTES.TEAM_ID,
|
||||
attributeKey: 'team_id',
|
||||
value: 'team_id',
|
||||
attributeName: t('FILTER.ATTRIBUTES.TEAM_NAME'),
|
||||
label: t('FILTER.ATTRIBUTES.TEAM_NAME'),
|
||||
inputType: 'searchSelect',
|
||||
@@ -160,8 +137,8 @@ export function useConversationFilterContext() {
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONVERSATION_ATTRIBUTES.DISPLAY_ID,
|
||||
value: CONVERSATION_ATTRIBUTES.DISPLAY_ID,
|
||||
attributeKey: 'display_id',
|
||||
value: 'display_id',
|
||||
attributeName: t('FILTER.ATTRIBUTES.CONVERSATION_IDENTIFIER'),
|
||||
label: t('FILTER.ATTRIBUTES.CONVERSATION_IDENTIFIER'),
|
||||
inputType: 'plainText',
|
||||
@@ -170,8 +147,8 @@ export function useConversationFilterContext() {
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONVERSATION_ATTRIBUTES.CAMPAIGN_ID,
|
||||
value: CONVERSATION_ATTRIBUTES.CAMPAIGN_ID,
|
||||
attributeKey: 'campaign_id',
|
||||
value: 'campaign_id',
|
||||
attributeName: t('FILTER.ATTRIBUTES.CAMPAIGN_NAME'),
|
||||
label: t('FILTER.ATTRIBUTES.CAMPAIGN_NAME'),
|
||||
inputType: 'searchSelect',
|
||||
@@ -184,8 +161,8 @@ export function useConversationFilterContext() {
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONVERSATION_ATTRIBUTES.LABELS,
|
||||
value: CONVERSATION_ATTRIBUTES.LABELS,
|
||||
attributeKey: 'labels',
|
||||
value: 'labels',
|
||||
attributeName: t('FILTER.ATTRIBUTES.LABELS'),
|
||||
label: t('FILTER.ATTRIBUTES.LABELS'),
|
||||
inputType: 'multiSelect',
|
||||
@@ -208,8 +185,8 @@ export function useConversationFilterContext() {
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONVERSATION_ATTRIBUTES.BROWSER_LANGUAGE,
|
||||
value: CONVERSATION_ATTRIBUTES.BROWSER_LANGUAGE,
|
||||
attributeKey: 'browser_language',
|
||||
value: 'browser_language',
|
||||
attributeName: t('FILTER.ATTRIBUTES.BROWSER_LANGUAGE'),
|
||||
label: t('FILTER.ATTRIBUTES.BROWSER_LANGUAGE'),
|
||||
inputType: 'searchSelect',
|
||||
@@ -219,8 +196,8 @@ export function useConversationFilterContext() {
|
||||
attributeModel: 'additional',
|
||||
},
|
||||
{
|
||||
attributeKey: CONVERSATION_ATTRIBUTES.COUNTRY_CODE,
|
||||
value: CONVERSATION_ATTRIBUTES.COUNTRY_CODE,
|
||||
attributeKey: 'country_code',
|
||||
value: 'country_code',
|
||||
attributeName: t('FILTER.ATTRIBUTES.COUNTRY_NAME'),
|
||||
label: t('FILTER.ATTRIBUTES.COUNTRY_NAME'),
|
||||
inputType: 'searchSelect',
|
||||
@@ -230,8 +207,8 @@ export function useConversationFilterContext() {
|
||||
attributeModel: 'additional',
|
||||
},
|
||||
{
|
||||
attributeKey: CONVERSATION_ATTRIBUTES.REFERER,
|
||||
value: CONVERSATION_ATTRIBUTES.REFERER,
|
||||
attributeKey: 'referer',
|
||||
value: 'referer',
|
||||
attributeName: t('FILTER.ATTRIBUTES.REFERER_LINK'),
|
||||
label: t('FILTER.ATTRIBUTES.REFERER_LINK'),
|
||||
inputType: 'plainText',
|
||||
@@ -240,8 +217,8 @@ export function useConversationFilterContext() {
|
||||
attributeModel: 'additional',
|
||||
},
|
||||
{
|
||||
attributeKey: CONVERSATION_ATTRIBUTES.CREATED_AT,
|
||||
value: CONVERSATION_ATTRIBUTES.CREATED_AT,
|
||||
attributeKey: 'created_at',
|
||||
value: 'created_at',
|
||||
attributeName: t('FILTER.ATTRIBUTES.CREATED_AT'),
|
||||
label: t('FILTER.ATTRIBUTES.CREATED_AT'),
|
||||
inputType: 'date',
|
||||
@@ -250,8 +227,8 @@ export function useConversationFilterContext() {
|
||||
attributeModel: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: CONVERSATION_ATTRIBUTES.LAST_ACTIVITY_AT,
|
||||
value: CONVERSATION_ATTRIBUTES.LAST_ACTIVITY_AT,
|
||||
attributeKey: 'last_activity_at',
|
||||
value: 'last_activity_at',
|
||||
attributeName: t('FILTER.ATTRIBUTES.LAST_ACTIVITY'),
|
||||
label: t('FILTER.ATTRIBUTES.LAST_ACTIVITY'),
|
||||
inputType: 'date',
|
||||
|
||||
@@ -26,7 +26,7 @@ const { t } = useI18n();
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="absolute bg-n-alpha-3 px-4 py-3 border rounded-xl border-n-strong text-n-slate-12 bottom-6 w-52 text-xs backdrop-blur-[100px] shadow-[0px_0px_24px_0px_rgba(0,0,0,0.12)] opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all break-all"
|
||||
class="absolute bg-n-alpha-3 px-4 py-3 border rounded-xl border-n-strong text-n-slate-12 bottom-6 w-52 text-xs backdrop-blur-[100px] shadow-[0px_0px_24px_0px_rgba(0,0,0,0.12)] opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all"
|
||||
:class="{
|
||||
'ltr:left-0 rtl:right-0': orientation === ORIENTATION.LEFT,
|
||||
'ltr:right-0 rtl:left-0': orientation === ORIENTATION.RIGHT,
|
||||
|
||||
@@ -49,7 +49,7 @@ const isSent = computed(() => {
|
||||
isASmsInbox.value ||
|
||||
isATelegramChannel.value
|
||||
) {
|
||||
return sourceId.value && status.value === MESSAGE_STATUS.SENT;
|
||||
return status.value === MESSAGE_STATUS.SENT;
|
||||
}
|
||||
|
||||
// All messages will be mark as sent for the Line channel, as there is no source ID.
|
||||
@@ -67,7 +67,7 @@ const isDelivered = computed(() => {
|
||||
isASmsInbox.value ||
|
||||
isAFacebookInbox.value
|
||||
) {
|
||||
return sourceId.value && status.value === MESSAGE_STATUS.DELIVERED;
|
||||
return status.value === MESSAGE_STATUS.DELIVERED;
|
||||
}
|
||||
// All messages marked as delivered for the web widget inbox and API inbox once they are sent.
|
||||
if (isAWebWidgetInbox.value || isAPIInbox.value) {
|
||||
@@ -88,7 +88,7 @@ const isRead = computed(() => {
|
||||
isATwilioChannel.value ||
|
||||
isAFacebookInbox.value
|
||||
) {
|
||||
return sourceId.value && status.value === MESSAGE_STATUS.READ;
|
||||
return status.value === MESSAGE_STATUS.READ;
|
||||
}
|
||||
|
||||
if (isAWebWidgetInbox.value || isAPIInbox.value) {
|
||||
@@ -102,7 +102,6 @@ const statusToShow = computed(() => {
|
||||
if (isRead.value) return MESSAGE_STATUS.READ;
|
||||
if (isDelivered.value) return MESSAGE_STATUS.DELIVERED;
|
||||
if (isSent.value) return MESSAGE_STATUS.SENT;
|
||||
if (status.value === MESSAGE_STATUS.FAILED) return MESSAGE_STATUS.FAILED;
|
||||
|
||||
return MESSAGE_STATUS.PROGRESS;
|
||||
});
|
||||
|
||||
@@ -9,7 +9,6 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
const { websocketURL = '' } = window.chatwootConfig || {};
|
||||
super(app, pubsubToken, websocketURL);
|
||||
this.CancelTyping = [];
|
||||
this.conversationEventTimestamps = new Map();
|
||||
this.events = {
|
||||
'message.created': this.onMessageCreated,
|
||||
'message.updated': this.onMessageUpdated,
|
||||
@@ -34,23 +33,6 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a conversation event is out of sequence by comparing timestamps
|
||||
* @param {number} conversationId - The ID of the conversation to check
|
||||
* @param {number} eventTimestamp - The timestamp of the current event
|
||||
* @returns {boolean} Returns true if event is out of sequence, false otherwise
|
||||
*/
|
||||
eventOutOfSequence(conversationId, eventTimestamp) {
|
||||
if (!eventTimestamp) return false;
|
||||
const lastTimestamp = this.conversationEventTimestamps.get(conversationId);
|
||||
if (!lastTimestamp || eventTimestamp > lastTimestamp) {
|
||||
this.conversationEventTimestamps.set(conversationId, eventTimestamp);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
onReconnect = () => {
|
||||
emitter.emit(BUS_EVENTS.WEBSOCKET_RECONNECT);
|
||||
@@ -86,10 +68,8 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
}
|
||||
};
|
||||
|
||||
onAssigneeChanged = (payload, eventTimestamp) => {
|
||||
onAssigneeChanged = payload => {
|
||||
const { id } = payload;
|
||||
if (this.eventOutOfSequence(id, eventTimestamp)) return;
|
||||
|
||||
if (id) {
|
||||
this.app.$store.dispatch('updateConversation', payload);
|
||||
}
|
||||
@@ -101,8 +81,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
this.fetchConversationStats();
|
||||
};
|
||||
|
||||
onConversationRead = (data, eventTimestamp) => {
|
||||
if (this.eventOutOfSequence(data.id, eventTimestamp)) return;
|
||||
onConversationRead = data => {
|
||||
this.app.$store.dispatch('updateConversation', data);
|
||||
};
|
||||
|
||||
@@ -125,14 +104,12 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
onReload = () => window.location.reload();
|
||||
|
||||
onStatusChange = (data, eventTimestamp) => {
|
||||
if (this.eventOutOfSequence(data.id, eventTimestamp)) return;
|
||||
onStatusChange = data => {
|
||||
this.app.$store.dispatch('updateConversation', data);
|
||||
this.fetchConversationStats();
|
||||
};
|
||||
|
||||
onConversationUpdated = (data, eventTimestamp) => {
|
||||
if (this.eventOutOfSequence(data.id, eventTimestamp)) return;
|
||||
onConversationUpdated = data => {
|
||||
this.app.$store.dispatch('updateConversation', data);
|
||||
this.fetchConversationStats();
|
||||
};
|
||||
|
||||
@@ -560,7 +560,7 @@
|
||||
"ERROR_MESSAGE": "Wir konnten die Suche nicht abschließen. Bitte versuch es erneut."
|
||||
},
|
||||
"FORM": {
|
||||
"GO_TO_CONVERSATION": "Anzeigen",
|
||||
"GO_TO_CONVERSATION": "Aussicht",
|
||||
"SUCCESS_MESSAGE": "Die Nachricht wurde erfolgreich versendet!",
|
||||
"ERROR_MESSAGE": "Beim Erstellen der Unterhaltung ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.",
|
||||
"NO_INBOX_ALERT": "Es sind keine Posteingänge vorhanden, um eine Unterhaltung mit diesem Kontakt zu starten.",
|
||||
|
||||
@@ -751,7 +751,7 @@
|
||||
"WHATSAPP": "WhatsApp",
|
||||
"SMS": "SMS",
|
||||
"EMAIL": "E-Mail",
|
||||
"TELEGRAM": "Telegramm",
|
||||
"TELEGRAM": "Telegram",
|
||||
"LINE": "Line",
|
||||
"API": "API-Kanal"
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"SUBSCRIBED_EVENTS": "Abonnierte Events",
|
||||
"LEARN_MORE": "Mehr über Webhooks erfahren",
|
||||
"LEARN_MORE": "Learn more about webhooks",
|
||||
"FORM": {
|
||||
"CANCEL": "Stornieren",
|
||||
"DESC": "Webhook-Ereignisse bieten Ihnen Echtzeitinformationen darüber, was in Ihrem Chatwoot-Konto passiert. Bitte geben Sie eine gültige URL ein, um einen Rückruf zu konfigurieren.",
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"END_POINT": {
|
||||
"LABEL": "Webhook-URL",
|
||||
"PLACEHOLDER": "Beispiel: {webhookExampleURL}",
|
||||
"PLACEHOLDER": "Example: {webhookExampleURL}",
|
||||
"ERROR": "Bitte geben Sie eine gültige URL ein"
|
||||
},
|
||||
"EDIT_SUBMIT": "Webhook aktualisieren",
|
||||
@@ -114,7 +114,7 @@
|
||||
},
|
||||
"OPEN_AI": {
|
||||
"AI_ASSIST": "AI-Assistent",
|
||||
"WITH_AI": " {option} mit KI ",
|
||||
"WITH_AI": " {option} with AI ",
|
||||
"OPTIONS": {
|
||||
"REPLY_SUGGESTION": "Antwortvorschlag",
|
||||
"SUMMARIZE": "Zusammenfassen",
|
||||
@@ -235,7 +235,7 @@
|
||||
"ERROR": "Beim Abrufen der linearen Probleme ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut",
|
||||
"LINK_SUCCESS": "Problem erfolgreich verknüpft",
|
||||
"LINK_ERROR": "Beim Verknüpfen des Problems ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut",
|
||||
"LINK_TITLE": "Unterhaltung (#{conversationId}) mit {name}"
|
||||
"LINK_TITLE": "Conversation (#{conversationId}) with {name}"
|
||||
},
|
||||
"ADD_OR_LINK": {
|
||||
"TITLE": "Lineares Problem erstellen/verknüpfen",
|
||||
@@ -294,7 +294,7 @@
|
||||
"PRIORITY": "Priorität",
|
||||
"ASSIGNEE": "Zugewiesener",
|
||||
"LABELS": "Labels",
|
||||
"CREATED_AT": "Erstellt am {createdAt}"
|
||||
"CREATED_AT": "Created at {createdAt}"
|
||||
},
|
||||
"UNLINK": {
|
||||
"TITLE": "Verknüpfung aufheben",
|
||||
@@ -302,8 +302,8 @@
|
||||
"ERROR": "Beim Aufheben der Verknüpfung des Problems ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut"
|
||||
},
|
||||
"DELETE": {
|
||||
"TITLE": "Sind Sie sicher, dass Sie die Integration löschen möchten?",
|
||||
"MESSAGE": "Sind Sie sicher, dass Sie die Integration löschen möchten?",
|
||||
"TITLE": "Are you sure you want to delete the integration?",
|
||||
"MESSAGE": "Are you sure you want to delete the integration?",
|
||||
"CONFIRM": "Ja, löschen",
|
||||
"CANCEL": "Stornieren"
|
||||
}
|
||||
@@ -313,27 +313,27 @@
|
||||
"NAME": "Kapitän",
|
||||
"COPILOT": {
|
||||
"SEND_MESSAGE": "Nachricht senden...",
|
||||
"LOADER": "Captain denkt nach",
|
||||
"LOADER": "Captain is thinking",
|
||||
"YOU": "Sie",
|
||||
"USE": "Verwenden",
|
||||
"RESET": "Zurücksetzen",
|
||||
"SELECT_ASSISTANT": "Assistent auswählen"
|
||||
"USE": "Use this",
|
||||
"RESET": "Reset",
|
||||
"SELECT_ASSISTANT": "Select Assistant"
|
||||
},
|
||||
"PAYWALL": {
|
||||
"TITLE": "Upgrade auf Captain AI",
|
||||
"TITLE": "Upgrade to use Captain AI",
|
||||
"AVAILABLE_ON": "Captain is not available on the free plan.",
|
||||
"UPGRADE_PROMPT": "Tarif upgraden, um Zugang zu unseren Assistenten, Copilot und mehr zu erhalten.",
|
||||
"UPGRADE_NOW": "Jetzt upgraden",
|
||||
"CANCEL_ANYTIME": "Sie können Ihr Paket jederzeit ändern oder kündigen"
|
||||
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
|
||||
"UPGRADE_NOW": "Upgrade now",
|
||||
"CANCEL_ANYTIME": "You can change or cancel your plan anytime"
|
||||
},
|
||||
"ENTERPRISE_PAYWALL": {
|
||||
"AVAILABLE_ON": "Captain AI Funktion ist nur mit einem kostenpflichtigen Tarif verfügbar.",
|
||||
"UPGRADE_PROMPT": "Tarif upgraden, um Zugang zu unseren Assistenten, Copilot und mehr zu erhalten.",
|
||||
"ASK_ADMIN": "Bitte kontaktieren Sie Ihren Administrator für das Upgrade."
|
||||
"AVAILABLE_ON": "Captain AI feature is only available in a paid plan.",
|
||||
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
|
||||
"ASK_ADMIN": "Please reach out to your administrator for the upgrade."
|
||||
},
|
||||
"BANNER": {
|
||||
"RESPONSES": "You've used over 80% of your response limit. To continue using Captain AI, please upgrade.",
|
||||
"DOCUMENTS": "Dokumentenlimit erreicht. Upgraden um Cpatain AI weiter zu verwenden."
|
||||
"DOCUMENTS": "Document limit reached. Upgrade to continue using Captain AI."
|
||||
},
|
||||
"FORM": {
|
||||
"CANCEL": "Stornieren",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"NO_RESULTS": "Nenhum resultado encontrado."
|
||||
},
|
||||
"MULTI_SELECTOR": {
|
||||
"PLACEHOLDER": "Nenhum",
|
||||
"PLACEHOLDER": "Nenhuma",
|
||||
"TITLE": {
|
||||
"AGENT": "Selecionar agente",
|
||||
"TEAM": "Selecionar time"
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"AUDIT_LOGS": {
|
||||
"HEADER": "Auditoria",
|
||||
"HEADER_BTN_TXT": "Adicionar Logs de Auditoria",
|
||||
"HEADER": "Registros de Auditoria",
|
||||
"HEADER_BTN_TXT": "Adicionar Registros de Auditoria",
|
||||
"LOADING": "Buscando Logs de Auditoria",
|
||||
"DESCRIPTION": "Logs de Auditoria mantêm um registro de atividades em sua conta, permitindo que você acompanhe e auditoria de sua conta, equipe ou serviços.",
|
||||
"LEARN_MORE": "Saiba mais sobre os logs de auditoria",
|
||||
"SEARCH_404": "Não existem itens correspondentes a esta consulta",
|
||||
"SIDEBAR_TXT": "<p><b>Logs de Auditoria</b> </p><p> Os Logs de Auditoria são rastros para eventos e ações em um Sistema Chatwoot. </p>",
|
||||
"SIDEBAR_TXT": "<p><b>Registros de Auditoria</b> </p><p> Registros de Auditoria são trilhas para eventos e ações em um Sistema Chatwoot. </p>",
|
||||
"LIST": {
|
||||
"404": "Não há Logs de Auditoria disponíveis nesta conta.",
|
||||
"TITLE": "Gerenciar Logs de Auditoria",
|
||||
"DESC": "Logs de auditoria são rastros para eventos e ações em um Sistema de Chatwoot.",
|
||||
"404": "Não há Registros de Auditoria disponíveis nesta conta.",
|
||||
"TITLE": "Gerenciar Registros de Auditoria",
|
||||
"DESC": "Registros de Auditoria são trilhas para eventos e ações em um Sistema Chatwoot.",
|
||||
"TABLE_HEADER": {
|
||||
"ACTIVITY": "Usuário",
|
||||
"TIME": "Ação",
|
||||
|
||||
@@ -27,12 +27,12 @@
|
||||
"ERROR": "Falha ao atualizar etiquetas"
|
||||
},
|
||||
"CONVERSATION": {
|
||||
"TITLE": "Etiquetas da conversa",
|
||||
"ADD_BUTTON": "Adicionar etiquetas"
|
||||
"TITLE": "Marcador da conversa",
|
||||
"ADD_BUTTON": "Adicionar marcador"
|
||||
},
|
||||
"LABEL_SELECT": {
|
||||
"TITLE": "Adicionar etiquetas",
|
||||
"PLACEHOLDER": "Pesquisar etiquetas",
|
||||
"TITLE": "Adicionar marcador",
|
||||
"PLACEHOLDER": "Pesquisar marcador ",
|
||||
"NO_RESULT": "Nenhuma etiqueta encontrada",
|
||||
"CREATE_LABEL": "Criar etiqueta"
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
"HEADER": {
|
||||
"RESOLVE_ACTION": "Resolver",
|
||||
"REOPEN_ACTION": "Reabrir",
|
||||
"OPEN_ACTION": "Abrir",
|
||||
"OPEN_ACTION": "Abertas",
|
||||
"OPEN": "Mais",
|
||||
"CLOSE": "Fechar",
|
||||
"DETAILS": "detalhes",
|
||||
@@ -148,7 +148,7 @@
|
||||
"MESSAGE_SIGN_TOOLTIP": "Assinatura de mensagem",
|
||||
"ENABLE_SIGN_TOOLTIP": "Ativar assinatura",
|
||||
"DISABLE_SIGN_TOOLTIP": "Desativar assinatura",
|
||||
"MSG_INPUT": "Shift + enter para nova linha. Digite '/' para selecionar uma Resposta Pronta.",
|
||||
"MSG_INPUT": "Shift + enter para nova linha. Digite '/' para atalhos.",
|
||||
"PRIVATE_MSG_INPUT": "A mensagem será visível apenas para agentes",
|
||||
"MESSAGE_SIGNATURE_NOT_CONFIGURED": "A assinatura da mensagem não está configurada. Por favor, configure-a nas configurações do perfil.",
|
||||
"CLICK_HERE": "Clique aqui para atualizar",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"ENTERPRISE_PAYWALL": {
|
||||
"AVAILABLE_ON": "O recurso de função personalizada está disponível apenas nos planos pagos.",
|
||||
"UPGRADE_PROMPT": "Atualize para um plano pago para acessar recursos avançados como logs de auditoria, capacidade do agente e muito mais.",
|
||||
"UPGRADE_PROMPT": "Atualize para um plano pago para acessar recursos avançados como registros de auditoria, capacidade do agente e muito mais.",
|
||||
"ASK_ADMIN": "Entre em contato com seu administrador para fazer a atualização."
|
||||
},
|
||||
"LIST": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"GENERAL_SETTINGS": {
|
||||
"TITLE": "Conta",
|
||||
"TITLE": "Configurações da conta",
|
||||
"SUBMIT": "Atualizar configurações",
|
||||
"BACK": "Anterior",
|
||||
"DISMISS": "Recusar",
|
||||
@@ -131,10 +131,10 @@
|
||||
"GO_TO_INBOX_REPORTS": "Ir para Relatórios da Caixa de Entrada",
|
||||
"GO_TO_TEAM_REPORTS": "Ir para Relatórios da Equipe",
|
||||
"GO_TO_SETTINGS_AGENTS": "Ir para Configurações de Agente",
|
||||
"GO_TO_SETTINGS_TEAMS": "Ir para as Configurações de Equipe",
|
||||
"GO_TO_SETTINGS_INBOXES": "Ir para as Configurações da Caixa de Entrada",
|
||||
"GO_TO_SETTINGS_TEAMS": "Ir para as configurações de equipe",
|
||||
"GO_TO_SETTINGS_INBOXES": "Ir para as configurações da Caixa de Entrada",
|
||||
"GO_TO_SETTINGS_LABELS": "Ir para as Configurações de Etiqueta",
|
||||
"GO_TO_SETTINGS_CANNED_RESPONSES": "Ir para as Configurações de Respostas Prontas",
|
||||
"GO_TO_SETTINGS_CANNED_RESPONSES": "Ir para as configurações de respostas prontas",
|
||||
"GO_TO_SETTINGS_APPLICATIONS": "Vá para Configurações do Aplicativo",
|
||||
"GO_TO_SETTINGS_ACCOUNT": "Ir para as Configurações da Conta",
|
||||
"GO_TO_SETTINGS_PROFILE": "Ir para as Configurações do Perfil",
|
||||
|
||||
@@ -500,7 +500,7 @@
|
||||
"ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT": "As conversas continuarão sobre o e-mail se o endereço de e-mail de contato estiver disponível.",
|
||||
"LOCK_TO_SINGLE_CONVERSATION": "Bloquear para conversa única",
|
||||
"LOCK_TO_SINGLE_CONVERSATION_SUB_TEXT": "Ativar ou desativar várias conversas para o mesmo contato nesta caixa de entrada",
|
||||
"INBOX_UPDATE_TITLE": "Configurações da Caixa de Entrada",
|
||||
"INBOX_UPDATE_TITLE": "Configurações da Caixa de entrada",
|
||||
"INBOX_UPDATE_SUB_TEXT": "Atualize suas configurações de caixa de entrada",
|
||||
"AUTO_ASSIGNMENT_SUB_TEXT": "Ativar ou desativar a atribuição automática de novas conversas aos agentes adicionados a essa caixa de entrada.",
|
||||
"HMAC_VERIFICATION": "Validação de Identidade do Usuário",
|
||||
@@ -695,7 +695,7 @@
|
||||
"PLACE_HOLDER": "Fale conosco no chat"
|
||||
},
|
||||
"UPDATE": {
|
||||
"BUTTON_TEXT": "Atualizar Configurações do Widget",
|
||||
"BUTTON_TEXT": "Atualizar configurações do widget",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Configurações do widget atualizadas com sucesso",
|
||||
"ERROR_MESSAGE": "Não é possível atualizar as configurações do widget"
|
||||
|
||||
@@ -302,8 +302,8 @@
|
||||
"ERROR": "Houve um erro ao desvincular o atributo, por favor, tente novamente"
|
||||
},
|
||||
"DELETE": {
|
||||
"TITLE": "Tem certeza que deseja excluir esta integração?",
|
||||
"MESSAGE": "Tem certeza que deseja excluir esta integração?",
|
||||
"TITLE": "Are you sure you want to delete the integration?",
|
||||
"MESSAGE": "Are you sure you want to delete the integration?",
|
||||
"CONFIRM": "Sim, excluir",
|
||||
"CANCEL": "Cancelar"
|
||||
}
|
||||
@@ -317,7 +317,7 @@
|
||||
"YOU": "Você",
|
||||
"USE": "Use isto",
|
||||
"RESET": "Reiniciar",
|
||||
"SELECT_ASSISTANT": "Selecione o Assistente"
|
||||
"SELECT_ASSISTANT": "Select Assistant"
|
||||
},
|
||||
"PAYWALL": {
|
||||
"TITLE": "Atualize para usar o Capitão IA",
|
||||
@@ -440,19 +440,19 @@
|
||||
"DOCUMENTABLE": {
|
||||
"CONVERSATION": "Conversação #{id}"
|
||||
},
|
||||
"SELECTED": "{count} selecionado",
|
||||
"BULK_APPROVE_BUTTON": "Aprovar",
|
||||
"SELECTED": "{count} selected",
|
||||
"BULK_APPROVE_BUTTON": "Approve",
|
||||
"BULK_DELETE_BUTTON": "Excluir",
|
||||
"BULK_APPROVE": {
|
||||
"SUCCESS_MESSAGE": "Perguntas Frequentes aprovadas com sucesso",
|
||||
"ERROR_MESSAGE": "Ocorreu um erro ao aprovar as Perguntas Frequentes. Tente novamente."
|
||||
"SUCCESS_MESSAGE": "FAQs approved successfully",
|
||||
"ERROR_MESSAGE": "There was an error approving the FAQs, please try again."
|
||||
},
|
||||
"BULK_DELETE": {
|
||||
"TITLE": "Excluir as Perguntas Frequentes?",
|
||||
"DESCRIPTION": "Tem certeza que deseja excluir as Perguntas Frequentes selecionadas? Esta ação não pode ser desfeita.",
|
||||
"CONFIRM": "Sim, excluir todas",
|
||||
"SUCCESS_MESSAGE": "Perguntas Frequentes excluídas com sucesso/",
|
||||
"ERROR_MESSAGE": "Ocorreu um erro ao excluir as Perguntas Frequentes, por favor tente novamente."
|
||||
"TITLE": "Delete FAQs?",
|
||||
"DESCRIPTION": "Are you sure you want to delete the selected FAQs? This action cannot be undone.",
|
||||
"CONFIRM": "Yes, delete all",
|
||||
"SUCCESS_MESSAGE": "FAQs deleted successfully",
|
||||
"ERROR_MESSAGE": "There was an error deleting the FAQs, please try again."
|
||||
},
|
||||
"DELETE": {
|
||||
"TITLE": "Tem certeza que deseja excluir o FAQ?",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"LABEL_MGMT": {
|
||||
"HEADER": "Etiquetas",
|
||||
"HEADER_BTN_TXT": "Adicionar etiqueta",
|
||||
"HEADER_BTN_TXT": "Adicionar marcador",
|
||||
"LOADING": "Buscando etiquetas",
|
||||
"DESCRIPTION": "As etiquetas ajudam você a categorizar e priorizar conversas e leads. Você pode atribuir uma etiqueta a uma conversa ou contato usando o painel lateral.",
|
||||
"LEARN_MORE": "Aprenda mais sobre etiquetas",
|
||||
@@ -18,21 +18,21 @@
|
||||
},
|
||||
"FORM": {
|
||||
"NAME": {
|
||||
"LABEL": "Nome da Etiqueta",
|
||||
"PLACEHOLDER": "Nome da etiqueta",
|
||||
"REQUIRED_ERROR": "O nome da etiqueta é obrigatório",
|
||||
"LABEL": "Nome do marcador",
|
||||
"PLACEHOLDER": "Nome do marcador",
|
||||
"REQUIRED_ERROR": "O nome do marcador é obrigatório",
|
||||
"MINIMUM_LENGTH_ERROR": "Tamanho mínimo 2 é necessário",
|
||||
"VALID_ERROR": "Somente Letras, Números, Hífen e Sublinhado são permitidos"
|
||||
},
|
||||
"DESCRIPTION": {
|
||||
"LABEL": "Descrição",
|
||||
"PLACEHOLDER": "Descrição da etiqueta"
|
||||
"PLACEHOLDER": "Descrição do marcador"
|
||||
},
|
||||
"COLOR": {
|
||||
"LABEL": "Cor"
|
||||
},
|
||||
"SHOW_ON_SIDEBAR": {
|
||||
"LABEL": "Exibir etiqueta na barra lateral"
|
||||
"LABEL": "Exibir marcador na barra lateral"
|
||||
},
|
||||
"EDIT": "Alterar",
|
||||
"CREATE": "Criar",
|
||||
@@ -54,10 +54,10 @@
|
||||
"SUGGESTED_LABELS": "Etiquetas sugeridos"
|
||||
},
|
||||
"ADD": {
|
||||
"TITLE": "Adicionar etiqueta",
|
||||
"TITLE": "Adicionar marcador",
|
||||
"DESC": "Etiquetas permitem agrupar as conversas.",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Etiqueta adicionada com sucesso",
|
||||
"SUCCESS_MESSAGE": "Marcador adicionado com sucesso",
|
||||
"ERROR_MESSAGE": "Ocorreu um erro, por favor tente novamente"
|
||||
}
|
||||
},
|
||||
@@ -71,7 +71,7 @@
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Excluir",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Etiqueta excluída com sucesso",
|
||||
"SUCCESS_MESSAGE": "Marcador excluído com sucesso",
|
||||
"ERROR_MESSAGE": "Ocorreu um erro, por favor tente novamente"
|
||||
},
|
||||
"CONFIRM": {
|
||||
|
||||
@@ -471,7 +471,7 @@
|
||||
"NO_AGENTS": "Não há conversas por agentes",
|
||||
"TABLE_HEADER": {
|
||||
"AGENT": "Agente",
|
||||
"OPEN": "Abrir",
|
||||
"OPEN": "Abertas",
|
||||
"UNATTENDED": "Não atendidas",
|
||||
"STATUS": "Situação"
|
||||
}
|
||||
@@ -509,7 +509,7 @@
|
||||
"SLA": "Nome do SLA",
|
||||
"AGENTS": "Nome do Agente",
|
||||
"INBOXES": "Nome da Caixa de Entrada",
|
||||
"LABELS": "Nome da etiqueta",
|
||||
"LABELS": "Nome do marcador",
|
||||
"TEAMS": "Nome da equipe"
|
||||
},
|
||||
"SLA": "Política SLA",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"UPDATE_SUCCESS": "Suas configurações foram atualizadas com sucesso",
|
||||
"CARD": {
|
||||
"ENTER_KEY": {
|
||||
"HEADING": "Enter (↵)",
|
||||
"HEADING": "Inserir (↵)",
|
||||
"CONTENT": "Enviar mensagens pressionando a tecla Enter em vez de clicar no botão de enviar."
|
||||
},
|
||||
"CMD_ENTER_KEY": {
|
||||
@@ -37,19 +37,19 @@
|
||||
},
|
||||
"INTERFACE_SECTION": {
|
||||
"TITLE": "Interface",
|
||||
"NOTE": "Personalize a aparência do seu painel do Chatwoot.",
|
||||
"NOTE": "Customize the look and feel of your Chatwoot dashboard.",
|
||||
"FONT_SIZE": {
|
||||
"TITLE": "Tamanho da fonte",
|
||||
"NOTE": "Ajuste o tamanho do texto do painel com base na sua preferência.",
|
||||
"UPDATE_SUCCESS": "As configurações de sua fonte foram atualizadas com sucesso",
|
||||
"UPDATE_ERROR": "Ocorreu um erro ao atualizar as configurações de fonte, por favor, tente novamente",
|
||||
"TITLE": "Font size",
|
||||
"NOTE": "Adjust the text size across the dashboard based on your preference.",
|
||||
"UPDATE_SUCCESS": "Your font settings have been updated successfully",
|
||||
"UPDATE_ERROR": "There is an error while updating the font settings, please try again",
|
||||
"OPTIONS": {
|
||||
"SMALLER": "Menor",
|
||||
"SMALL": "Pequeno",
|
||||
"SMALLER": "Smaller",
|
||||
"SMALL": "Small",
|
||||
"DEFAULT": "Padrão",
|
||||
"LARGE": "Grande",
|
||||
"LARGER": "Maior",
|
||||
"EXTRA_LARGE": "Muito Grande"
|
||||
"LARGE": "Large",
|
||||
"LARGER": "Larger",
|
||||
"EXTRA_LARGE": "Extra Large"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -287,13 +287,13 @@
|
||||
"HOME": "Principal",
|
||||
"AGENTS": "Agentes",
|
||||
"AGENT_BOTS": "Robôs",
|
||||
"AUDIT_LOGS": "Auditoria",
|
||||
"AUDIT_LOGS": "Registros de Auditoria",
|
||||
"INBOXES": "Caixas de Entrada",
|
||||
"NOTIFICATIONS": "Notificações",
|
||||
"CANNED_RESPONSES": "Respostas Prontas",
|
||||
"INTEGRATIONS": "Integrações",
|
||||
"PROFILE_SETTINGS": "Configurações do Perfil",
|
||||
"ACCOUNT_SETTINGS": "Conta",
|
||||
"ACCOUNT_SETTINGS": "Configurações da conta",
|
||||
"APPLICATIONS": "Aplicações",
|
||||
"LABELS": "Etiquetas",
|
||||
"CUSTOM_ATTRIBUTES": "Atributos Personalizados",
|
||||
@@ -322,7 +322,7 @@
|
||||
"REPORTS_INBOX": "Caixa de Entrada",
|
||||
"REPORTS_TEAM": "Equipe",
|
||||
"SET_AVAILABILITY_TITLE": "Defina como",
|
||||
"SET_YOUR_AVAILABILITY": "Disponibilidade",
|
||||
"SET_YOUR_AVAILABILITY": "Definir a sua disponibilidade",
|
||||
"SLA": "SLA",
|
||||
"CUSTOM_ROLES": "Regras Personalizadas",
|
||||
"BETA": "Beta",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"SLA": {
|
||||
"HEADER": "Acordo de Nível de Serviço",
|
||||
"HEADER": "Termos do Serviço",
|
||||
"ADD_ACTION": "Adicionar SLA",
|
||||
"ADD_ACTION_LONG": "Criar uma nova Política de SLA",
|
||||
"DESCRIPTION": "Acordo de Nível de Serviço (SLAs em inglês) são acordos que definem expectativas claras entre sua equipe e os clientes. Estabelecem normas para tempos de resposta e de resolução, criando um quadro de responsabilização e garantindo uma experiência coerente e de qualidade.",
|
||||
"DESCRIPTION": "Contratos de Nível de Serviço (SLAs em inglês) são contratos que definem expectativas claras entre sua equipe e os clientes. Estabelecem normas para tempos de resposta e de resolução, criando um quadro de responsabilização e garantindo uma experiência coerente e de qualidade.",
|
||||
"LEARN_MORE": "Saiba mais sobre SLA",
|
||||
"LOADING": "Buscando SLAs",
|
||||
"PAYWALL": {
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"ENTERPRISE_PAYWALL": {
|
||||
"AVAILABLE_ON": "O recurso SLA está disponível apenas nos planos pagos.",
|
||||
"UPGRADE_PROMPT": "Atualize para um plano pago para acessar recursos avançados como logs de auditoria, capacidade do agente e muito mais.",
|
||||
"UPGRADE_PROMPT": "Atualize para um plano pago para acessar recursos avançados como registros de auditoria, capacidade do agente e muito mais.",
|
||||
"ASK_ADMIN": "Entre em contato com seu administrador para fazer a atualização."
|
||||
},
|
||||
"LIST": {
|
||||
|
||||
@@ -266,7 +266,7 @@
|
||||
"ATTRIBUTE_WARNING": "Контактная информация <strong>{primaryContactName}</strong> будет скопирована в <strong>{parentContactName}</strong>."
|
||||
},
|
||||
"SEARCH": {
|
||||
"ERROR_MESSAGE": "Что-то пошло не так. Пожалуйста, попробуйте позже."
|
||||
"ERROR_MESSAGE": "Something went wrong. Please try again later."
|
||||
},
|
||||
"FORM": {
|
||||
"SUBMIT": " Объединить контакты",
|
||||
@@ -563,7 +563,7 @@
|
||||
"GO_TO_CONVERSATION": "Просмотр",
|
||||
"SUCCESS_MESSAGE": "Сообщение успешно отправлено!",
|
||||
"ERROR_MESSAGE": "Произошла ошибка при создании диалога. Пожалуйста, повторите попытку позже.",
|
||||
"NO_INBOX_ALERT": "Нет доступных входящих для начала разговора с этим контактом.",
|
||||
"NO_INBOX_ALERT": "Нет доступных \"входящих\" для начала разговора с этим контактом.",
|
||||
"CONTACT_SELECTOR": {
|
||||
"LABEL": "Кому:",
|
||||
"TAG_INPUT_PLACEHOLDER": "Поиск контакта с именем, email или номером телефона",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"PERMISSIONS": {
|
||||
"CONVERSATION_MANAGE": "Управление всеми диалогами",
|
||||
"CONVERSATION_UNASSIGNED_MANAGE": "Управление неназначенными беседами и теми, которые им назначены",
|
||||
"CONVERSATION_UNASSIGNED_MANAGE": "Управление неназначенными беседами и теми, которые назначены им",
|
||||
"CONVERSATION_PARTICIPATING_MANAGE": "Управление текущими беседами и теми, которые им назначены",
|
||||
"CONTACT_MANAGE": "Управление контактами",
|
||||
"REPORT_MANAGE": "Управление отчетами",
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
"CHANGE_PRIORITY": "Изменить приоритет",
|
||||
"CHANGE_TEAM": "Изменить команду",
|
||||
"SNOOZE_CONVERSATION": "Включить звук диалога",
|
||||
"ADD_LABEL": "Добавить метку в диалог",
|
||||
"ADD_LABEL": "Добавить метку в разговор",
|
||||
"REMOVE_LABEL": "Удалить метку из диалога",
|
||||
"SETTINGS": "Настройки",
|
||||
"AI_ASSIST": "Помощь ИИ",
|
||||
@@ -128,11 +128,11 @@
|
||||
"GO_TO_CONVERSATION_REPORTS": "Перейти к отчетам по диалогам",
|
||||
"GO_TO_AGENT_REPORTS": "Перейти к отчетам по сотрудникам",
|
||||
"GO_TO_LABEL_REPORTS": "Перейти к отчетам по меткам",
|
||||
"GO_TO_INBOX_REPORTS": "Перейти к отчётам по «Входящим»",
|
||||
"GO_TO_INBOX_REPORTS": "Перейти к отчётам по \"Входящим\"",
|
||||
"GO_TO_TEAM_REPORTS": "Перейти к отчетам по командам",
|
||||
"GO_TO_SETTINGS_AGENTS": "Перейти к настройкам сотрудников",
|
||||
"GO_TO_SETTINGS_TEAMS": "Перейти к настройкам команд",
|
||||
"GO_TO_SETTINGS_INBOXES": "Перейти к настройкам «Входящих»",
|
||||
"GO_TO_SETTINGS_INBOXES": "Перейти к настройкам \"Входящих\"",
|
||||
"GO_TO_SETTINGS_LABELS": "Перейти к настройкам меток",
|
||||
"GO_TO_SETTINGS_CANNED_RESPONSES": "Перейти к настройкам шаблонных ответов",
|
||||
"GO_TO_SETTINGS_APPLICATIONS": "Перейти в настройки приложения",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"INBOX_MGMT": {
|
||||
"HEADER": "Источники",
|
||||
"DESCRIPTION": "Канал — это способ коммуникации, который ваш клиент выбирает для связи с вами. Входящие — это место, где вы управляете диалогами для определенного канала. Он может включать диалоги из различных источников, таких как электронная почта, онлайн чат или социальные сети.",
|
||||
"LEARN_MORE": "Узнать больше о «Входящих»",
|
||||
"LEARN_MORE": "Узнать больше о \"Входящих\"",
|
||||
"RECONNECTION_REQUIRED": "Входящие сообщения отключены. Вы не будете получать новые сообщения пока не пройдете авторизацию повторно.",
|
||||
"CLICK_TO_RECONNECT": "Нажмите здесь для повторного подключения.",
|
||||
"LIST": {
|
||||
@@ -208,8 +208,8 @@
|
||||
}
|
||||
},
|
||||
"WHATSAPP": {
|
||||
"TITLE": "Канал WhatsApp",
|
||||
"DESC": "Начните поддерживать своих клиентов через WhatsApp.",
|
||||
"TITLE": "WhatsApp канал",
|
||||
"DESC": "Начните поддерживать ваших клиентов через WhatsApp.",
|
||||
"PROVIDERS": {
|
||||
"LABEL": "Поставщик API",
|
||||
"TWILIO": "Twilio",
|
||||
|
||||
@@ -302,8 +302,8 @@
|
||||
"ERROR": "Произошла ошибка при отвязке задачи, пожалуйста, попробуйте ещё раз"
|
||||
},
|
||||
"DELETE": {
|
||||
"TITLE": "Вы уверены, что хотите удалить интеграцию?",
|
||||
"MESSAGE": "Вы уверены, что хотите удалить интеграцию?",
|
||||
"TITLE": "Are you sure you want to delete the integration?",
|
||||
"MESSAGE": "Are you sure you want to delete the integration?",
|
||||
"CONFIRM": "Да, удалить",
|
||||
"CANCEL": "Отменить"
|
||||
}
|
||||
@@ -317,18 +317,18 @@
|
||||
"YOU": "Вы",
|
||||
"USE": "Использовать это",
|
||||
"RESET": "Сброс",
|
||||
"SELECT_ASSISTANT": "Выбрать ассистента"
|
||||
"SELECT_ASSISTANT": "Select Assistant"
|
||||
},
|
||||
"PAYWALL": {
|
||||
"TITLE": "Обновите тарифный план, чтобы использовать Captain AI",
|
||||
"AVAILABLE_ON": "Капитан недоступен на бесплатном тарифном плане.",
|
||||
"UPGRADE_PROMPT": "Обновите тарифный план, чтобы получить доступ к нашим ассистентам, copilot и другим функциям.",
|
||||
"UPGRADE_PROMPT": "Обновите тарифный план, чтобы получить доступ к помощникам, Captain AI и другому.",
|
||||
"UPGRADE_NOW": "Обновить сейчас",
|
||||
"CANCEL_ANYTIME": "Вы можете изменить или отменить ваш тарифный план в любое время"
|
||||
},
|
||||
"ENTERPRISE_PAYWALL": {
|
||||
"AVAILABLE_ON": "Функция Captain AI доступна только в платном плане.",
|
||||
"UPGRADE_PROMPT": "Обновите тарифный план, чтобы получить доступ к нашим ассистентам, copilot и другим функциям.",
|
||||
"UPGRADE_PROMPT": "Обновите тарифный план, чтобы получить доступ к помощникам, Captain AI и другому.",
|
||||
"ASK_ADMIN": "Пожалуйста, обратитесь к вашему администратору для обновления."
|
||||
},
|
||||
"BANNER": {
|
||||
@@ -341,35 +341,35 @@
|
||||
"EDIT": "Обновить"
|
||||
},
|
||||
"ASSISTANTS": {
|
||||
"HEADER": "Ассистенты",
|
||||
"ADD_NEW": "Создать нового ассистента",
|
||||
"HEADER": "Помощники",
|
||||
"ADD_NEW": "Создать нового помощника",
|
||||
"DELETE": {
|
||||
"TITLE": "Вы уверены, что хотите удалить ассистента?",
|
||||
"DESCRIPTION": "Это действие необратимо. Удаление этого ассистента удалит его из всех подключенных источников и навсегда удалит все сгенерированные знания.",
|
||||
"TITLE": "Вы уверены, что хотите удалить помощника?",
|
||||
"DESCRIPTION": "Это действие необратимо. Удаление этого помощника удалит его из всех подключенных источников и навсегда удалит все сгенерированные знания.",
|
||||
"CONFIRM": "Да, удалить",
|
||||
"SUCCESS_MESSAGE": "Ассистент успешно удален",
|
||||
"ERROR_MESSAGE": "При удалении ассистента произошла ошибка. Пожалуйста, попробуйте еще раз."
|
||||
"SUCCESS_MESSAGE": "Помощник успешно удален",
|
||||
"ERROR_MESSAGE": "При удалении помощника произошла ошибка. Пожалуйста, попробуйте еще раз."
|
||||
},
|
||||
"FORM_DESCRIPTION": "Заполните приведенные ниже сведения, чтобы назвать своего ассистента, описать его цель и указать поддерживаемый продукт.",
|
||||
"FORM_DESCRIPTION": "Заполните приведенные ниже сведения, чтобы назвать своего помощника, описать его цель и указать поддерживаемый продукт.",
|
||||
"CREATE": {
|
||||
"TITLE": "Создать ассистента",
|
||||
"SUCCESS_MESSAGE": "Ассистент успешно создан",
|
||||
"ERROR_MESSAGE": "При создании ассистента произошла ошибка, пожалуйста, попробуйте еще раз."
|
||||
"TITLE": "Создать помощника",
|
||||
"SUCCESS_MESSAGE": "Помощник успешно создан",
|
||||
"ERROR_MESSAGE": "При создании помощника произошла ошибка, пожалуйста, попробуйте еще раз."
|
||||
},
|
||||
"FORM": {
|
||||
"NAME": {
|
||||
"LABEL": "Имя ассистента",
|
||||
"PLACEHOLDER": "Введите имя ассистента",
|
||||
"ERROR": "Пожалуйста, укажите имя ассистента"
|
||||
"LABEL": "Имя помощника",
|
||||
"PLACEHOLDER": "Введите имя помощника",
|
||||
"ERROR": "Пожалуйста, укажите имя помощника"
|
||||
},
|
||||
"DESCRIPTION": {
|
||||
"LABEL": "Описание ассистента",
|
||||
"PLACEHOLDER": "Опишите, как и где будет использоваться этот ассистент",
|
||||
"LABEL": "Описание помощника",
|
||||
"PLACEHOLDER": "Опишите, как и где будет использоваться этот помощник",
|
||||
"ERROR": "Необходимо описание"
|
||||
},
|
||||
"PRODUCT_NAME": {
|
||||
"LABEL": "Название продукта",
|
||||
"PLACEHOLDER": "Введите название продукта, для которого предназначен этот ассистент",
|
||||
"PLACEHOLDER": "Введите название продукта, для которого предназначен этот помощник",
|
||||
"ERROR": "Требуется название продукта"
|
||||
},
|
||||
"FEATURES": {
|
||||
@@ -379,18 +379,18 @@
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"TITLE": "Обновить ассистента",
|
||||
"SUCCESS_MESSAGE": "Ассистент успешно обновлен",
|
||||
"ERROR_MESSAGE": "При обновлении ассистента произошла ошибка, пожалуйста, попробуйте еще раз."
|
||||
"TITLE": "Обновить помощника",
|
||||
"SUCCESS_MESSAGE": "Помощник успешно обновлен",
|
||||
"ERROR_MESSAGE": "При обновлении помощника произошла ошибка, пожалуйста, попробуйте еще раз."
|
||||
},
|
||||
"OPTIONS": {
|
||||
"EDIT_ASSISTANT": "Редактировать ассистента",
|
||||
"DELETE_ASSISTANT": "Удалить ассистента",
|
||||
"EDIT_ASSISTANT": "Редактировать помощника",
|
||||
"DELETE_ASSISTANT": "Удалить помощника",
|
||||
"VIEW_CONNECTED_INBOXES": "Просмотр подключенных источников"
|
||||
},
|
||||
"EMPTY_STATE": {
|
||||
"TITLE": "Нет доступных ассистентов",
|
||||
"SUBTITLE": "Создайте ассистента, чтобы дать быстрые и точные ответы пользователям. Он может учиться на ваших статьях из центра поддержки и прошлых диалогах."
|
||||
"TITLE": "Нет доступных помощников",
|
||||
"SUBTITLE": "Создайте помощника, чтобы дать быстрые и точные ответы пользователям. Он может учиться на ваших статьях из центра поддержки и прошлых диалогах."
|
||||
}
|
||||
},
|
||||
"DOCUMENTS": {
|
||||
@@ -413,9 +413,9 @@
|
||||
"ERROR": "Пожалуйста, укажите корректный URL для документа"
|
||||
},
|
||||
"ASSISTANT": {
|
||||
"LABEL": "Ассистент",
|
||||
"PLACEHOLDER": "Выберите ассистента",
|
||||
"ERROR": "Необходимо заполнить поле ассистента"
|
||||
"LABEL": "Помощник",
|
||||
"PLACEHOLDER": "Выберите помощника",
|
||||
"ERROR": "Необходимо заполнить поле помощника"
|
||||
}
|
||||
},
|
||||
"DELETE": {
|
||||
@@ -440,19 +440,19 @@
|
||||
"DOCUMENTABLE": {
|
||||
"CONVERSATION": "Диалог #{id}"
|
||||
},
|
||||
"SELECTED": "Выбрано {count}",
|
||||
"BULK_APPROVE_BUTTON": "Одобрить",
|
||||
"SELECTED": "{count} selected",
|
||||
"BULK_APPROVE_BUTTON": "Approve",
|
||||
"BULK_DELETE_BUTTON": "Удалить",
|
||||
"BULK_APPROVE": {
|
||||
"SUCCESS_MESSAGE": "FAQ успешно одобрены",
|
||||
"ERROR_MESSAGE": "Произошла ошибка при одобрении FAQ, пожалуйста, попробуйте ещё раз."
|
||||
"SUCCESS_MESSAGE": "FAQs approved successfully",
|
||||
"ERROR_MESSAGE": "There was an error approving the FAQs, please try again."
|
||||
},
|
||||
"BULK_DELETE": {
|
||||
"TITLE": "Удалить FAQ?",
|
||||
"DESCRIPTION": "Вы уверены, что хотите удалить выбранные FAQ? Это действие невозможно отменить.",
|
||||
"CONFIRM": "Да, удалить всё",
|
||||
"SUCCESS_MESSAGE": "FAQ успешно удалены",
|
||||
"ERROR_MESSAGE": "Произошла ошибка при удалении FAQ, пожалуйста, попробуйте ещё раз."
|
||||
"TITLE": "Delete FAQs?",
|
||||
"DESCRIPTION": "Are you sure you want to delete the selected FAQs? This action cannot be undone.",
|
||||
"CONFIRM": "Yes, delete all",
|
||||
"SUCCESS_MESSAGE": "FAQs deleted successfully",
|
||||
"ERROR_MESSAGE": "There was an error deleting the FAQs, please try again."
|
||||
},
|
||||
"DELETE": {
|
||||
"TITLE": "Вы действительно хотите удалить FAQ?",
|
||||
@@ -462,7 +462,7 @@
|
||||
"ERROR_MESSAGE": "Произошла ошибка при удалении FAQ, попробуйте еще раз."
|
||||
},
|
||||
"FILTER": {
|
||||
"ASSISTANT": "Ассистент: {selected}",
|
||||
"ASSISTANT": "Помощник: {selected}",
|
||||
"STATUS": "Статус: {selected}",
|
||||
"ALL_ASSISTANTS": "Все"
|
||||
},
|
||||
@@ -472,7 +472,7 @@
|
||||
"APPROVED": "Одобрено",
|
||||
"ALL": "Все"
|
||||
},
|
||||
"FORM_DESCRIPTION": "Добавьте вопрос и соответствующий ему ответ в базу знаний и выберите ассистента, с которым он должен связаться.",
|
||||
"FORM_DESCRIPTION": "Добавьте вопрос и соответствующий ему ответ в базу знаний и выберите помощника, с которым он должен связаться.",
|
||||
"CREATE": {
|
||||
"TITLE": "Добавить FAQ",
|
||||
"SUCCESS_MESSAGE": "Ответ успешно добавлен.",
|
||||
@@ -487,59 +487,59 @@
|
||||
"ANSWER": {
|
||||
"LABEL": "Ответ",
|
||||
"PLACEHOLDER": "Введите ответ",
|
||||
"ERROR": "Пожалуйста, введите корректный ответ."
|
||||
"ERROR": "Please provide a valid answer."
|
||||
},
|
||||
"ASSISTANT": {
|
||||
"LABEL": "Ассистент",
|
||||
"PLACEHOLDER": "Выбрать ассистента",
|
||||
"ERROR": "Выбрать ассистента"
|
||||
"LABEL": "Помощник",
|
||||
"PLACEHOLDER": "Select an assistant",
|
||||
"ERROR": "Please select an assistant."
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"TITLE": "Обновить FAQ",
|
||||
"SUCCESS_MESSAGE": "FAQ успешно обновлён",
|
||||
"ERROR_MESSAGE": "Произошла ошибка при обновлении FAQ, пожалуйста, попробуйте ещё раз",
|
||||
"APPROVE_SUCCESS_MESSAGE": "FAQ был отмечен как одобренный"
|
||||
"TITLE": "Update the FAQ",
|
||||
"SUCCESS_MESSAGE": "The FAQ has been successfully updated",
|
||||
"ERROR_MESSAGE": "There was an error updating the FAQ, please try again",
|
||||
"APPROVE_SUCCESS_MESSAGE": "The FAQ was marked as approved"
|
||||
},
|
||||
"OPTIONS": {
|
||||
"APPROVE": "Отметить как одобренный",
|
||||
"EDIT_RESPONSE": "FAQ отмечен как одобренный",
|
||||
"DELETE_RESPONSE": "Удалить FAQ"
|
||||
"APPROVE": "Mark as approved",
|
||||
"EDIT_RESPONSE": "Edit FAQ",
|
||||
"DELETE_RESPONSE": "Delete FAQ"
|
||||
},
|
||||
"EMPTY_STATE": {
|
||||
"TITLE": "FAQ не найдены",
|
||||
"SUBTITLE": "FAQ помогают вашему ассистенту быстро и точно отвечать на вопросы клиентов. Их можно генерировать автоматически из вашего контента или добавлять вручную."
|
||||
"TITLE": "No FAQs Found",
|
||||
"SUBTITLE": "FAQs help your assistant provide quick and accurate answers to questions from your customers. They can be generated automatically from your content or can be added manually."
|
||||
}
|
||||
},
|
||||
"INBOXES": {
|
||||
"HEADER": "Подключённые источники входящих",
|
||||
"ADD_NEW": "Подключить новый источник входящих",
|
||||
"HEADER": "Connected Inboxes",
|
||||
"ADD_NEW": "Connect a new inbox",
|
||||
"OPTIONS": {
|
||||
"DISCONNECT": "Отключиться"
|
||||
},
|
||||
"DELETE": {
|
||||
"TITLE": "Вы уверены, что хотите отключить этот источник входящих?",
|
||||
"TITLE": "Are you sure to disconnect the inbox?",
|
||||
"DESCRIPTION": "",
|
||||
"CONFIRM": "Да, удалить",
|
||||
"SUCCESS_MESSAGE": "Источник входящих успешно отключён.",
|
||||
"ERROR_MESSAGE": "Произошла ошибка при отключении источника входящих, пожалуйста, попробуйте ещё раз."
|
||||
"SUCCESS_MESSAGE": "The inbox was successfully disconnected.",
|
||||
"ERROR_MESSAGE": "There was an error disconnecting the inbox, please try again."
|
||||
},
|
||||
"FORM_DESCRIPTION": "Выберите источник входящих для подключения к ассистенту.",
|
||||
"FORM_DESCRIPTION": "Choose an inbox to connect with the assistant.",
|
||||
"CREATE": {
|
||||
"TITLE": "Подключить источник входящих",
|
||||
"SUCCESS_MESSAGE": "Источник входящих успешно подключён.",
|
||||
"ERROR_MESSAGE": "Произошла ошибка при подключении источника входящих. Пожалуйста, попробуйте ещё раз."
|
||||
"TITLE": "Connect an Inbox",
|
||||
"SUCCESS_MESSAGE": "The inbox was successfully connected.",
|
||||
"ERROR_MESSAGE": "An error occurred while connecting the inbox. Please try again."
|
||||
},
|
||||
"FORM": {
|
||||
"INBOX": {
|
||||
"LABEL": "Электронная почта",
|
||||
"PLACEHOLDER": "Выберите источник входящих для развертывания ассистента.",
|
||||
"ERROR": "Необходимо выбрать источник входящих."
|
||||
"PLACEHOLDER": "Choose the inbox to deploy the assistant.",
|
||||
"ERROR": "An inbox selection is required."
|
||||
}
|
||||
},
|
||||
"EMPTY_STATE": {
|
||||
"TITLE": "Нет подключённых источников входящих",
|
||||
"SUBTITLE": "Подключение источника входящих позволяет ассистенту обрабатывать первые вопросы ваших клиентов до их передачи вам."
|
||||
"TITLE": "No Connected Inboxes",
|
||||
"SUBTITLE": "Connecting an inbox allows the assistant to handle initial questions from your customers before transferring them to you."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
},
|
||||
"AGENT_REPORTS": {
|
||||
"HEADER": "Обзор агентов",
|
||||
"DESCRIPTION": "Легко отслеживайте эффективность работы агентов с помощью ключевых метрик, таких как количество бесед, время ответа, время решения проблем и количество решённых случаев. Нажмите на имя агента, чтобы узнать подробности.",
|
||||
"DESCRIPTION": "Easily track agent performance with key metrics such as conversations, response times, resolution times, and resolved cases. Click an agent’s name to learn more.",
|
||||
"LOADING_CHART": "Загрузка данных графика...",
|
||||
"NO_ENOUGH_DATA": "Недостаточно данных для создания отчета, пожалуйста, повторите попытку позже.",
|
||||
"DOWNLOAD_AGENT_REPORTS": "Сказать отчёт по агентам",
|
||||
@@ -259,7 +259,7 @@
|
||||
},
|
||||
"INBOX_REPORTS": {
|
||||
"HEADER": "Обзор входящих",
|
||||
"DESCRIPTION": "Быстро просматривайте эффективность источника входящих с основными метриками — количеством бесед, временем ответа, временем решения проблем и количеством решённых случаев — всё в одном месте. Нажмите на имя источника для получения подробной информации.",
|
||||
"DESCRIPTION": "Quickly view your inbox performance with key metrics like conversations, response times, resolution times, and resolved cases—all in one place. Click an inbox name for more details.",
|
||||
"LOADING_CHART": "Загрузка данных графика...",
|
||||
"NO_ENOUGH_DATA": "Недостаточно данных для создания отчета, пожалуйста, повторите попытку позже.",
|
||||
"DOWNLOAD_INBOX_REPORTS": "Скачать отчет по входящим",
|
||||
@@ -327,7 +327,7 @@
|
||||
},
|
||||
"TEAM_REPORTS": {
|
||||
"HEADER": "Обзор команды",
|
||||
"DESCRIPTION": "Получите обзор работы вашей команды с основными метриками, такими как количество бесед, время ответа, время решения проблем и количество решённых случаев. Нажмите на имя команды для подробностей.",
|
||||
"DESCRIPTION": "Get a snapshot of your team’s performance with essential metrics, including conversations, response times, resolution times, and resolved cases. Click a team name for more details.",
|
||||
"LOADING_CHART": "Загрузка данных графика...",
|
||||
"NO_ENOUGH_DATA": "Недостаточно данных для создания отчета, пожалуйста, повторите попытку позже.",
|
||||
"DOWNLOAD_TEAM_REPORTS": "Скачать отчет по команде",
|
||||
@@ -546,9 +546,9 @@
|
||||
"INBOX": "Электронная почта",
|
||||
"AGENT": "Оператор",
|
||||
"TEAM": "Команда",
|
||||
"AVG_RESOLUTION_TIME": "Среднее время решения",
|
||||
"AVG_FIRST_RESPONSE_TIME": "Среднее время первого ответа",
|
||||
"AVG_REPLY_TIME": "Среднее время ожидания клиента",
|
||||
"AVG_RESOLUTION_TIME": "Avg. Resolution Time",
|
||||
"AVG_FIRST_RESPONSE_TIME": "Avg. First Response Time",
|
||||
"AVG_REPLY_TIME": "Avg. Customer Waiting Time",
|
||||
"RESOLUTION_COUNT": "Количество завершенных",
|
||||
"CONVERSATIONS": "Количество диалогов"
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
"CONVERSATIONS": "Диалоги",
|
||||
"MESSAGES": "Сообщения"
|
||||
},
|
||||
"VIEW_MORE": "Посмотреть больше",
|
||||
"LOAD_MORE": "Загрузить ещё",
|
||||
"VIEW_MORE": "View more",
|
||||
"LOAD_MORE": "Load more",
|
||||
"SEARCHING_DATA": "Идёт поиск",
|
||||
"LOADING_DATA": "Загрузка",
|
||||
"EMPTY_STATE": "Не найдено {item} для запроса '{query}'",
|
||||
|
||||
@@ -36,20 +36,20 @@
|
||||
}
|
||||
},
|
||||
"INTERFACE_SECTION": {
|
||||
"TITLE": "Интерфейс",
|
||||
"NOTE": "Настройте внешний вид и оформление вашей панели Chatwoot.",
|
||||
"TITLE": "Interface",
|
||||
"NOTE": "Customize the look and feel of your Chatwoot dashboard.",
|
||||
"FONT_SIZE": {
|
||||
"TITLE": "Размер шрифта",
|
||||
"NOTE": "Измените размер текста на всей панели в соответствии с вашими предпочтениями.",
|
||||
"UPDATE_SUCCESS": "Настройки шрифта успешно обновлены",
|
||||
"UPDATE_ERROR": "Произошла ошибка при обновлении настроек шрифта, пожалуйста, попробуйте ещё раз",
|
||||
"TITLE": "Font size",
|
||||
"NOTE": "Adjust the text size across the dashboard based on your preference.",
|
||||
"UPDATE_SUCCESS": "Your font settings have been updated successfully",
|
||||
"UPDATE_ERROR": "There is an error while updating the font settings, please try again",
|
||||
"OPTIONS": {
|
||||
"SMALLER": "Меньше",
|
||||
"SMALL": "Маленький",
|
||||
"SMALLER": "Smaller",
|
||||
"SMALL": "Small",
|
||||
"DEFAULT": "По умолчанию",
|
||||
"LARGE": "Большой",
|
||||
"LARGER": "Крупнее",
|
||||
"EXTRA_LARGE": "Очень большой"
|
||||
"LARGE": "Large",
|
||||
"LARGER": "Larger",
|
||||
"EXTRA_LARGE": "Extra Large"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -162,7 +162,7 @@
|
||||
"REQUEST_PUSH": "Включить push-уведомления",
|
||||
"SLA_MISSED_FIRST_RESPONSE": "Отправить push-уведомление, если диалог не соответствует SLA первого ответа",
|
||||
"SLA_MISSED_NEXT_RESPONSE": "Отправить push-уведомление, если диалог не успел получить следующий ответ SLA",
|
||||
"SLA_MISSED_RESOLUTION": "Отправить push-уведомление, если диалог не соответствует SLA"
|
||||
"SLA_MISSED_RESOLUTION": "Отправить push-уведомление, если диалог не соответствует уровню разрешения SLA"
|
||||
},
|
||||
"PROFILE_IMAGE": {
|
||||
"LABEL": "Изображение профиля"
|
||||
@@ -281,9 +281,9 @@
|
||||
"SETTINGS": "Настройки",
|
||||
"CONTACTS": "Контакты",
|
||||
"CAPTAIN": "Капитан",
|
||||
"CAPTAIN_ASSISTANTS": "Ассистенты",
|
||||
"CAPTAIN_ASSISTANTS": "Помощники",
|
||||
"CAPTAIN_DOCUMENTS": "Документы",
|
||||
"CAPTAIN_RESPONSES": "FAQ",
|
||||
"CAPTAIN_RESPONSES": "FAQs",
|
||||
"HOME": "Главная",
|
||||
"AGENTS": "Операторы",
|
||||
"AGENT_BOTS": "Боты",
|
||||
@@ -345,14 +345,14 @@
|
||||
},
|
||||
"BILLING_SETTINGS": {
|
||||
"TITLE": "Платёж",
|
||||
"DESCRIPTION": "Управляйте подпиской здесь, обновите тарифный план и получите больше возможностей для вашей команды.",
|
||||
"DESCRIPTION": "Manage your subscription here, upgrade your plan and get more for your team.",
|
||||
"CURRENT_PLAN": {
|
||||
"TITLE": "Текущий план",
|
||||
"PLAN_NOTE": "На данный момент вы подписаны на ** план{plan}** с **{quantity}** лицензиями",
|
||||
"SEAT_COUNT": "Количество мест",
|
||||
"RENEWS_ON": "Продление"
|
||||
"SEAT_COUNT": "Number of seats",
|
||||
"RENEWS_ON": "Renews on"
|
||||
},
|
||||
"VIEW_PRICING": "Посмотреть цены",
|
||||
"VIEW_PRICING": "View Pricing",
|
||||
"MANAGE_SUBSCRIPTION": {
|
||||
"TITLE": "Управление подпиской",
|
||||
"DESCRIPTION": "Просматривайте ваши предыдущие счета, редактируйте платежные реквизиты или отмените подписку.",
|
||||
@@ -360,11 +360,11 @@
|
||||
},
|
||||
"CAPTAIN": {
|
||||
"TITLE": "Капитан",
|
||||
"DESCRIPTION": "Управляйте использованием и кредитами для Captain AI.",
|
||||
"BUTTON_TXT": "Купить дополнительные кредиты",
|
||||
"DESCRIPTION": "Manage usage and credits for Captain AI.",
|
||||
"BUTTON_TXT": "Buy more credits",
|
||||
"DOCUMENTS": "Документы",
|
||||
"RESPONSES": "Ответы",
|
||||
"UPGRADE": "Captain недоступен в бесплатном тарифном плане, обновите его, чтобы получить доступ к ассистентам, copilot и другим функциям."
|
||||
"RESPONSES": "Responses",
|
||||
"UPGRADE": "Captain is not available on the free plan, upgrade now to get access to assistants, copilot and more."
|
||||
},
|
||||
"CHAT_WITH_US": {
|
||||
"TITLE": "Нужна помощь?",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { mapGetters } from 'vuex';
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
|
||||
import NextSidebar from 'next/sidebar/Sidebar.vue';
|
||||
import Sidebar from '../../components/layout/Sidebar.vue';
|
||||
import WootKeyShortcutModal from 'dashboard/components/widgets/modal/WootKeyShortcutModal.vue';
|
||||
import AddAccountModal from 'dashboard/components/layout/sidebarComponents/AddAccountModal.vue';
|
||||
import AccountSelector from 'dashboard/components/layout/sidebarComponents/AccountSelector.vue';
|
||||
@@ -19,10 +20,6 @@ import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
const CommandBar = defineAsyncComponent(
|
||||
() => import('./commands/commandbar.vue')
|
||||
);
|
||||
|
||||
const Sidebar = defineAsyncComponent(
|
||||
() => import('../../components/layout/Sidebar.vue')
|
||||
);
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -121,6 +121,8 @@ export default {
|
||||
this.isALineChannel ||
|
||||
this.isAPIInbox ||
|
||||
(this.isAnEmailChannel && !this.inbox.provider) ||
|
||||
this.isAMicrosoftInbox ||
|
||||
this.isAGoogleInbox ||
|
||||
this.isAWhatsAppChannel ||
|
||||
this.isAWebWidgetInbox
|
||||
) {
|
||||
|
||||
@@ -39,14 +39,13 @@ export default {
|
||||
},
|
||||
mixins: [globalConfigMixin],
|
||||
setup() {
|
||||
const { isEditorHotKeyEnabled, updateUISettings } = useUISettings();
|
||||
const { isEditorHotKeyEnabled } = useUISettings();
|
||||
const { currentFontSize, updateFontSize } = useFontSize();
|
||||
|
||||
return {
|
||||
currentFontSize,
|
||||
updateFontSize,
|
||||
isEditorHotKeyEnabled,
|
||||
updateUISettings,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -79,10 +79,10 @@ class BaseActionCableConnector {
|
||||
this.consumer.disconnect();
|
||||
}
|
||||
|
||||
onReceived = ({ event, data, event_timestamp } = {}) => {
|
||||
onReceived = ({ event, data } = {}) => {
|
||||
if (this.isAValidEvent(data)) {
|
||||
if (this.events[event] && typeof this.events[event] === 'function') {
|
||||
this.events[event](data, event_timestamp);
|
||||
this.events[event](data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,11 +10,11 @@ class ActionCableBroadcastJob < ApplicationJob
|
||||
CONVERSATION_STATUS_CHANGED
|
||||
].freeze
|
||||
|
||||
def perform(members, event_name, data, event_timestamp)
|
||||
def perform(members, event_name, data)
|
||||
return if members.blank?
|
||||
|
||||
broadcast_data = prepare_broadcast_data(event_name, data)
|
||||
broadcast_to_members(members, event_name, broadcast_data, event_timestamp)
|
||||
broadcast_to_members(members, event_name, broadcast_data)
|
||||
end
|
||||
|
||||
private
|
||||
@@ -30,17 +30,13 @@ class ActionCableBroadcastJob < ApplicationJob
|
||||
conversation.push_event_data.merge(account_id: data[:account_id])
|
||||
end
|
||||
|
||||
# A timestamp is added to the broadcast data, this timestamp is
|
||||
# generated in the main app thread when the job is enqueued.
|
||||
# This is used as a "sequence number" to ensure we ignore out of order events
|
||||
def broadcast_to_members(members, event_name, broadcast_data, event_timestamp)
|
||||
def broadcast_to_members(members, event_name, broadcast_data)
|
||||
members.each do |member|
|
||||
ActionCable.server.broadcast(
|
||||
member,
|
||||
{
|
||||
event: event_name,
|
||||
data: broadcast_data,
|
||||
event_timestamp: event_timestamp
|
||||
data: broadcast_data
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
@@ -2,15 +2,8 @@ class Inboxes::FetchImapEmailInboxesJob < ApplicationJob
|
||||
queue_as :scheduled_jobs
|
||||
|
||||
def perform
|
||||
email_inboxes = Inbox.where(channel_type: 'Channel::Email')
|
||||
email_inboxes.find_each(batch_size: 100) do |inbox|
|
||||
::Inboxes::FetchImapEmailsJob.perform_later(inbox.channel) if should_fetch_emails?(inbox)
|
||||
Inbox.where(channel_type: 'Channel::Email').all.find_each(batch_size: 100) do |inbox|
|
||||
::Inboxes::FetchImapEmailsJob.perform_later(inbox.channel) if inbox.channel.imap_enabled
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def should_fetch_emails?(inbox)
|
||||
inbox.channel.imap_enabled && !inbox.account.suspended?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -58,8 +58,7 @@ class ActionCableListener < BaseListener
|
||||
conversation, account = extract_conversation_and_account(event)
|
||||
tokens = user_tokens(account, conversation.inbox.members) + contact_inbox_tokens(conversation.contact_inbox)
|
||||
|
||||
# Include the original event timestamp to ensure correct ordering on frontend
|
||||
broadcast(account, tokens, CONVERSATION_CREATED, conversation.push_event_data, event.timestamp.to_f)
|
||||
broadcast(account, tokens, CONVERSATION_CREATED, conversation.push_event_data)
|
||||
end
|
||||
|
||||
def conversation_read(event)
|
||||
@@ -73,16 +72,14 @@ class ActionCableListener < BaseListener
|
||||
conversation, account = extract_conversation_and_account(event)
|
||||
tokens = user_tokens(account, conversation.inbox.members) + contact_inbox_tokens(conversation.contact_inbox)
|
||||
|
||||
# Include the original event timestamp to ensure correct ordering on frontend
|
||||
broadcast(account, tokens, CONVERSATION_STATUS_CHANGED, conversation.push_event_data, event.timestamp.to_f)
|
||||
broadcast(account, tokens, CONVERSATION_STATUS_CHANGED, conversation.push_event_data)
|
||||
end
|
||||
|
||||
def conversation_updated(event)
|
||||
conversation, account = extract_conversation_and_account(event)
|
||||
tokens = user_tokens(account, conversation.inbox.members) + contact_inbox_tokens(conversation.contact_inbox)
|
||||
|
||||
# Include the original event timestamp to ensure correct ordering on frontend
|
||||
broadcast(account, tokens, CONVERSATION_UPDATED, conversation.push_event_data, event.timestamp.to_f)
|
||||
broadcast(account, tokens, CONVERSATION_UPDATED, conversation.push_event_data)
|
||||
end
|
||||
|
||||
def conversation_typing_on(event)
|
||||
@@ -121,16 +118,14 @@ class ActionCableListener < BaseListener
|
||||
conversation, account = extract_conversation_and_account(event)
|
||||
tokens = user_tokens(account, conversation.inbox.members)
|
||||
|
||||
# Include the original event timestamp to ensure correct ordering on frontend
|
||||
broadcast(account, tokens, ASSIGNEE_CHANGED, conversation.push_event_data, event.timestamp.to_f)
|
||||
broadcast(account, tokens, ASSIGNEE_CHANGED, conversation.push_event_data)
|
||||
end
|
||||
|
||||
def team_changed(event)
|
||||
conversation, account = extract_conversation_and_account(event)
|
||||
tokens = user_tokens(account, conversation.inbox.members)
|
||||
|
||||
# Include the original event timestamp to ensure correct ordering on frontend
|
||||
broadcast(account, tokens, TEAM_CHANGED, conversation.push_event_data, event.timestamp.to_f)
|
||||
broadcast(account, tokens, TEAM_CHANGED, conversation.push_event_data)
|
||||
end
|
||||
|
||||
def conversation_contact_changed(event)
|
||||
@@ -202,7 +197,7 @@ class ActionCableListener < BaseListener
|
||||
contact_inbox.hmac_verified? ? contact.contact_inboxes.where(hmac_verified: true).filter_map(&:pubsub_token) : [contact_inbox.pubsub_token]
|
||||
end
|
||||
|
||||
def broadcast(account, tokens, event_name, data, event_timestamp = nil)
|
||||
def broadcast(account, tokens, event_name, data)
|
||||
return if tokens.blank?
|
||||
|
||||
payload = data.merge(account_id: account.id)
|
||||
@@ -210,6 +205,6 @@ class ActionCableListener < BaseListener
|
||||
# Useful in cases like conversation assignment for generating a notification with assigner name.
|
||||
payload[:performer] = Current.user&.push_event_data if Current.user.present?
|
||||
|
||||
::ActionCableBroadcastJob.perform_later(tokens.uniq, event_name, payload, event_timestamp)
|
||||
::ActionCableBroadcastJob.perform_later(tokens.uniq, event_name, payload)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,7 +37,7 @@ class Channel::Sms < ApplicationRecord
|
||||
body = message_body(contact_number, message.content)
|
||||
body['media'] = message.attachments.map(&:download_url) if message.attachments.present?
|
||||
|
||||
send_to_bandwidth(body, message)
|
||||
send_to_bandwidth(body)
|
||||
end
|
||||
|
||||
def send_text_message(contact_number, message_content)
|
||||
@@ -56,7 +56,7 @@ class Channel::Sms < ApplicationRecord
|
||||
}
|
||||
end
|
||||
|
||||
def send_to_bandwidth(body, message = nil)
|
||||
def send_to_bandwidth(body)
|
||||
response = HTTParty.post(
|
||||
"#{api_base_path}/users/#{provider_config['account_id']}/messages",
|
||||
basic_auth: bandwidth_auth,
|
||||
@@ -64,22 +64,7 @@ class Channel::Sms < ApplicationRecord
|
||||
body: body.to_json
|
||||
)
|
||||
|
||||
if response.success?
|
||||
response.parsed_response['id']
|
||||
else
|
||||
handle_error(response, message)
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def handle_error(response, message)
|
||||
Rails.logger.error("[#{account_id}] Error sending SMS: #{response.parsed_response['description']}")
|
||||
return if message.blank?
|
||||
|
||||
# https://dev.bandwidth.com/apis/messaging-apis/messaging/#tag/Messages/operation/createMessage
|
||||
message.external_error = response.parsed_response['description']
|
||||
message.status = :failed
|
||||
message.save!
|
||||
response.success? ? response.parsed_response['id'] : nil
|
||||
end
|
||||
|
||||
def bandwidth_auth
|
||||
|
||||
@@ -22,12 +22,6 @@
|
||||
# index_custom_attribute_definitions_on_account_id (account_id)
|
||||
#
|
||||
class CustomAttributeDefinition < ApplicationRecord
|
||||
STANDARD_ATTRIBUTES = {
|
||||
:conversation => %w[status priority assignee_id inbox_id team_id display_id campaign_id labels browser_language country_code referer created_at
|
||||
last_activity_at],
|
||||
:contact => %w[name email phone_number identifier country_code city created_at last_activity_at referer blocked]
|
||||
}.freeze
|
||||
|
||||
scope :with_attribute_model, ->(attribute_model) { attribute_model.presence && where(attribute_model: attribute_model) }
|
||||
validates :attribute_display_name, presence: true
|
||||
|
||||
@@ -37,7 +31,6 @@ class CustomAttributeDefinition < ApplicationRecord
|
||||
|
||||
validates :attribute_display_type, presence: true
|
||||
validates :attribute_model, presence: true
|
||||
validate :attribute_must_not_conflict, on: :create
|
||||
|
||||
enum attribute_model: { conversation_attribute: 0, contact_attribute: 1 }
|
||||
enum attribute_display_type: { text: 0, number: 1, currency: 2, percent: 3, link: 4, date: 5, list: 6, checkbox: 7 }
|
||||
@@ -55,11 +48,4 @@ class CustomAttributeDefinition < ApplicationRecord
|
||||
def update_widget_pre_chat_custom_fields
|
||||
::Inboxes::UpdateWidgetPreChatCustomFieldsJob.perform_later(account, self)
|
||||
end
|
||||
|
||||
def attribute_must_not_conflict
|
||||
model_keys = attribute_model.to_sym == :conversation_attribute ? :conversation : :contact
|
||||
return unless attribute_key.in?(STANDARD_ATTRIBUTES[model_keys])
|
||||
|
||||
errors.add(:attribute_key, I18n.t('errors.custom_attribute_definition.key_conflict'))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,30 +20,15 @@ class Facebook::SendOnFacebookService < Base::SendOnChannelService
|
||||
end
|
||||
|
||||
def send_message_to_facebook(delivery_params)
|
||||
parsed_result = deliver_message(delivery_params)
|
||||
return if parsed_result.nil?
|
||||
|
||||
result = Facebook::Messenger::Bot.deliver(delivery_params, page_id: channel.page_id)
|
||||
parsed_result = JSON.parse(result)
|
||||
if parsed_result['error'].present?
|
||||
message.update!(status: :failed, external_error: external_error(parsed_result))
|
||||
Rails.logger.info "Facebook::SendOnFacebookService: Error sending message to Facebook : Page - #{channel.page_id} : #{parsed_result}"
|
||||
Rails.logger.info "Facebook::SendOnFacebookService: Error sending message to Facebook : Page - #{channel.page_id} : #{result}"
|
||||
end
|
||||
|
||||
message.update!(source_id: parsed_result['message_id']) if parsed_result['message_id'].present?
|
||||
end
|
||||
|
||||
def deliver_message(delivery_params)
|
||||
result = Facebook::Messenger::Bot.deliver(delivery_params, page_id: channel.page_id)
|
||||
JSON.parse(result)
|
||||
rescue JSON::ParserError
|
||||
message.update!(status: :failed, external_error: 'Facebook was unable to process this request')
|
||||
Rails.logger.error "Facebook::SendOnFacebookService: Error parsing JSON response from Facebook : Page - #{channel.page_id} : #{result}"
|
||||
nil
|
||||
rescue Net::OpenTimeout
|
||||
message.update!(status: :failed, external_error: 'Request timed out, please try again later')
|
||||
Rails.logger.error "Facebook::SendOnFacebookService: Timeout error sending message to Facebook : Page - #{channel.page_id}"
|
||||
nil
|
||||
end
|
||||
|
||||
def fb_text_message_params
|
||||
{
|
||||
recipient: { id: contact.get_source_id(inbox.id) },
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
require 'json'
|
||||
|
||||
class FilterService
|
||||
include Filters::FilterHelper
|
||||
include FilterHelper
|
||||
include CustomExceptions::CustomFilter
|
||||
|
||||
ATTRIBUTE_MODEL = 'conversation_attribute'.freeze
|
||||
@@ -43,15 +43,18 @@ class FilterService
|
||||
end
|
||||
|
||||
def filter_values(query_hash)
|
||||
attribute_key = query_hash['attribute_key']
|
||||
values = query_hash['values']
|
||||
case query_hash['attribute_key']
|
||||
when 'status'
|
||||
return Conversation.statuses.values if query_hash['values'].include?('all')
|
||||
|
||||
return conversation_status_values(values) if attribute_key == 'status'
|
||||
return conversation_priority_values(values) if attribute_key == 'priority'
|
||||
return message_type_values(values) if attribute_key == 'message_type'
|
||||
return downcase_array_values(values) if attribute_key == 'content'
|
||||
|
||||
case_insensitive_values(query_hash)
|
||||
query_hash['values'].map { |x| Conversation.statuses[x.to_sym] }
|
||||
when 'message_type'
|
||||
query_hash['values'].map { |x| Message.message_types[x.to_sym] }
|
||||
when 'content'
|
||||
downcase_array_values(query_hash['values'])
|
||||
else
|
||||
case_insensitive_values(query_hash)
|
||||
end
|
||||
end
|
||||
|
||||
def downcase_array_values(values)
|
||||
|
||||
@@ -70,28 +70,22 @@ class Instagram::SendOnInstagramService < Base::SendOnChannelService
|
||||
query: query
|
||||
)
|
||||
|
||||
handle_response(response, message_content)
|
||||
end
|
||||
|
||||
def handle_response(response, message_content)
|
||||
parsed_response = response.parsed_response
|
||||
if response.success? && parsed_response['error'].blank?
|
||||
message.update!(source_id: parsed_response['message_id'])
|
||||
|
||||
parsed_response
|
||||
else
|
||||
external_error = external_error(parsed_response)
|
||||
Rails.logger.error("Instagram response: #{external_error} : #{message_content}")
|
||||
message.update!(status: :failed, external_error: external_error)
|
||||
|
||||
nil
|
||||
if response[:error].present?
|
||||
Rails.logger.error("Instagram response: #{response['error']} : #{message_content}")
|
||||
message.status = :failed
|
||||
message.external_error = external_error(response)
|
||||
end
|
||||
|
||||
message.source_id = response['message_id'] if response['message_id'].present?
|
||||
message.save!
|
||||
|
||||
response
|
||||
end
|
||||
|
||||
def external_error(response)
|
||||
# https://developers.facebook.com/docs/instagram-api/reference/error-codes/
|
||||
error_message = response.dig('error', 'message')
|
||||
error_code = response.dig('error', 'code')
|
||||
error_message = response[:error][:message]
|
||||
error_code = response[:error][:code]
|
||||
|
||||
"#{error_code} - #{error_message}"
|
||||
end
|
||||
|
||||
@@ -27,33 +27,6 @@ class Whatsapp::Providers::BaseService
|
||||
raise 'Overwrite this method in child class'
|
||||
end
|
||||
|
||||
def error_message
|
||||
raise 'Overwrite this method in child class'
|
||||
end
|
||||
|
||||
def process_response(response)
|
||||
parsed_response = response.parsed_response
|
||||
if response.success? && parsed_response['error'].blank?
|
||||
parsed_response['messages'].first['id']
|
||||
else
|
||||
handle_error(response)
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def handle_error(response)
|
||||
Rails.logger.error response.body
|
||||
return if @message.blank?
|
||||
|
||||
# https://developers.facebook.com/docs/whatsapp/cloud-api/support/error-codes/#sample-response
|
||||
error_message = error_message(response)
|
||||
return if error_message.blank?
|
||||
|
||||
@message.external_error = error_message
|
||||
@message.status = :failed
|
||||
@message.save!
|
||||
end
|
||||
|
||||
def create_buttons(items)
|
||||
buttons = []
|
||||
items.each do |item|
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
class Whatsapp::Providers::Whatsapp360DialogService < Whatsapp::Providers::BaseService
|
||||
def send_message(phone_number, message)
|
||||
@message = message
|
||||
if message.attachments.present?
|
||||
send_attachment_message(phone_number, message)
|
||||
elsif message.content_type == 'input_select'
|
||||
@@ -79,7 +78,6 @@ class Whatsapp::Providers::Whatsapp360DialogService < Whatsapp::Providers::BaseS
|
||||
}
|
||||
type_content['caption'] = message.content unless %w[audio sticker].include?(type)
|
||||
type_content['filename'] = attachment.file.filename if type == 'document'
|
||||
|
||||
response = HTTParty.post(
|
||||
"#{api_base_path}/messages",
|
||||
headers: api_headers,
|
||||
@@ -93,9 +91,13 @@ class Whatsapp::Providers::Whatsapp360DialogService < Whatsapp::Providers::BaseS
|
||||
process_response(response)
|
||||
end
|
||||
|
||||
def error_message(response)
|
||||
# {"meta": {"success": false, "http_code": 400, "developer_message": "errro-message", "360dialog_trace_id": "someid"}}
|
||||
response.parsed_response.dig('meta', 'developer_message')
|
||||
def process_response(response)
|
||||
if response.success?
|
||||
response['messages'].first['id']
|
||||
else
|
||||
Rails.logger.error response.body
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def template_body_parameters(template_info)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseService
|
||||
def send_message(phone_number, message)
|
||||
@message = message
|
||||
|
||||
if message.attachments.present?
|
||||
send_attachment_message(phone_number, message)
|
||||
elsif message.content_type == 'input_select'
|
||||
@@ -113,9 +111,13 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi
|
||||
process_response(response)
|
||||
end
|
||||
|
||||
def error_message(response)
|
||||
# https://developers.facebook.com/docs/whatsapp/cloud-api/support/error-codes/#sample-response
|
||||
response.parsed_response&.dig('error', 'message')
|
||||
def process_response(response)
|
||||
if response.success?
|
||||
response['messages'].first['id']
|
||||
else
|
||||
Rails.logger.error response.body
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def template_body_parameters(template_info)
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
json.meta do
|
||||
json.total_count @attachments_count
|
||||
end
|
||||
|
||||
json.payload @attachments do |attachment|
|
||||
json.message_id attachment.push_event_data[:message_id]
|
||||
json.thumb_url attachment.push_event_data[:thumb_url]
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
<% content_for(:title) do %>
|
||||
Configure Settings - <%= @config.titleize %>
|
||||
<% end %>
|
||||
|
||||
<header class="main-content__header" role="banner">
|
||||
<h1 class="main-content__page-title" id="page-title">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
.eye-icon.eye-hide path {
|
||||
d: path('M3 3l18 18M10.5 10.677a2 2 0 002.823 2.823M7.362 7.561C5.68 8.74 4.279 10.42 3 12c1.889 2.991 5.282 6 9 6 1.55 0 3.043-.523 4.395-1.35M12 6c4.008 0 6.701 3.009 9 6a15.66 15.66 0 01-1.078 1.5');
|
||||
}
|
||||
</style>
|
||||
|
||||
<section class="main-content__body">
|
||||
<%= form_with url: super_admin_app_config_url(config: @config) , method: :post do |form| %>
|
||||
<% @allowed_configs.each do |key| %>
|
||||
@@ -23,36 +15,18 @@
|
||||
</div>
|
||||
<div class="-mt-2 field-unit__field ">
|
||||
<% if @installation_configs[key]&.dig('type') == 'boolean' %>
|
||||
<%= form.select "app_config[#{key}]",
|
||||
[["True", true], ["False", false]],
|
||||
<%= form.select "app_config[#{key}]",
|
||||
[["True", true], ["False", false]],
|
||||
{ selected: ActiveModel::Type::Boolean.new.cast(@app_config[key]) },
|
||||
class: "mt-2 border border-slate-100 p-1 rounded-md"
|
||||
class: "mt-2 border border-slate-100 p-1 rounded-md"
|
||||
%>
|
||||
<% elsif @installation_configs[key]&.dig('type') == 'code' %>
|
||||
<%= form.text_area "app_config[#{key}]",
|
||||
value: @app_config[key],
|
||||
<%= form.text_area "app_config[#{key}]",
|
||||
value: @app_config[key],
|
||||
rows: 12,
|
||||
wrap: 'off',
|
||||
class: "mt-2 border font-mono text-xs border-slate-100 p-1 rounded-md overflow-scroll"
|
||||
%>
|
||||
<% elsif @installation_configs[key]&.dig('type') == 'secret' %>
|
||||
<div class="relative">
|
||||
<%= form.password_field "app_config[#{key}]",
|
||||
id: "app_config_#{key}",
|
||||
value: @app_config[key],
|
||||
class: "mt-2 border border-slate-100 p-1.5 pr-8 rounded-md w-full"
|
||||
%>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute reset-base !bg-white top-1/2 !outline-0 !text-n-slate-11 -translate-y-1/2 right-2 p-1 hover:!bg-n-slate-5 rounded-sm toggle-password"
|
||||
data-target="app_config_<%= key %>"
|
||||
>
|
||||
<svg class="eye-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 5C5.63636 5 2 12 2 12C2 12 5.63636 19 12 19C18.3636 19 22 12 22 12C22 12 18.3636 5 12 5Z"/>
|
||||
<path d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= form.text_field "app_config[#{key}]", value: @app_config[key] %>
|
||||
<% end %>
|
||||
@@ -69,26 +43,3 @@
|
||||
</div>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
<% content_for :javascript do %>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('.toggle-password').forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const targetId = button.dataset.target;
|
||||
const input = document.getElementById(targetId);
|
||||
const type = input.type === 'password' ? 'text' : 'password';
|
||||
input.type = type;
|
||||
|
||||
// Toggle icon
|
||||
const svg = button.querySelector('.eye-icon');
|
||||
if (type === 'password') {
|
||||
svg.classList.remove('eye-hide')
|
||||
} else {
|
||||
svg.classList.add('eye-hide')
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<% end %>
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<%#
|
||||
# Filters
|
||||
|
||||
This partial is used on the `index` page to display available filters
|
||||
for a collection of resources.
|
||||
|
||||
## Local variables:
|
||||
|
||||
- `page`:
|
||||
An instance of [Administrate::Page::Collection][1].
|
||||
Contains helper methods to help display a table,
|
||||
and knows which attributes should be displayed in the resource's table.
|
||||
|
||||
[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Collection
|
||||
%>
|
||||
|
||||
<%
|
||||
# Get the dashboard class name from the resource name
|
||||
resource_name = page.resource_name.classify
|
||||
dashboard_class_name = "#{resource_name}Dashboard"
|
||||
dashboard_class = dashboard_class_name.constantize
|
||||
|
||||
# Get the current filter if any
|
||||
current_filter = nil
|
||||
if params[:search] && params[:search].include?(':')
|
||||
current_filter = params[:search].split(':').first
|
||||
end
|
||||
%>
|
||||
|
||||
<% if dashboard_class.const_defined?(:COLLECTION_FILTERS) && !dashboard_class::COLLECTION_FILTERS.empty? %>
|
||||
<div class="flex items-center bg-gray-100 border-0 rounded-md shadow-none relative w-[260px]">
|
||||
<div class="flex items-center h-10 px-2 w-full">
|
||||
<div class="flex items-center justify-center flex-shrink-0 mr-2 text-gray-500" title="Filter by">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 h-full min-w-0 relative">
|
||||
<select id="filter-select" class="appearance-none bg-gray-100 border-0 text-gray-700 cursor-pointer text-sm h-full overflow-hidden truncate whitespace-nowrap w-full pr-7 pl-0 py-2 focus:outline-none bg-[url('data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2712%27 height=%2712%27 viewBox=%270 0 12 12%27%3E%3Cpath fill=%27%23293f54%27 d=%27M6 9L1 4h10z%27/%3E%3C/svg%3E')] bg-[right_0.25rem_center] bg-no-repeat bg-[length:0.75rem]" onchange="applyFilter(this.value)">
|
||||
<option value="">All records</option>
|
||||
<% dashboard_class::COLLECTION_FILTERS.each do |filter_name, _| %>
|
||||
<option value="<%= filter_name %>" <%= 'selected' if filter_name.to_s == current_filter %>>
|
||||
<%= filter_name.to_s.titleize %>
|
||||
</option>
|
||||
<% end %>
|
||||
</select>
|
||||
<% if current_filter %>
|
||||
<a href="?" class="flex items-center justify-center rounded-full text-gray-500 text-xl font-bold h-[18px] w-[18px] leading-none absolute right-5 top-1/2 -translate-y-1/2 no-underline z-2 hover:text-gray-900" title="Clear filter">×</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function applyFilter(filterName) {
|
||||
if (filterName) {
|
||||
window.location.href = "?search=" + encodeURIComponent(filterName) + "%3A";
|
||||
} else {
|
||||
window.location.href = "?";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<% end %>
|
||||
@@ -1,24 +0,0 @@
|
||||
<form class="search" role="search">
|
||||
<label class="search__label" for="search">
|
||||
<svg class="search__eyeglass-icon" role="img">
|
||||
<title>
|
||||
<%= t("administrate.search.label", resource: resource_name) %>
|
||||
</title>
|
||||
<use xlink:href="#icon-eyeglass" />
|
||||
</svg>
|
||||
</label>
|
||||
|
||||
<input class="search__input"
|
||||
id="search"
|
||||
type="search"
|
||||
name="search"
|
||||
placeholder="<%= t("administrate.search.label", resource: resource_name) %>"
|
||||
value="<%= search_term %>">
|
||||
|
||||
<%= link_to clear_search_params, class: "search__clear-link" do %>
|
||||
<svg class="search__clear-icon" role="img">
|
||||
<title><%= t("administrate.search.clear") %></title>
|
||||
<use xlink:href="#icon-cancel" />
|
||||
</svg>
|
||||
<% end %>
|
||||
</form>
|
||||
@@ -28,36 +28,27 @@ It renders the `_table` partial to display details about the resources.
|
||||
<% end %>
|
||||
|
||||
<header class="main-content__header" role="banner">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<h1 class="main-content__page-title m-0 mr-6" id="page-title">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
<h1 class="main-content__page-title" id="page-title">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
|
||||
<div class="flex items-center">
|
||||
<% if show_search_bar %>
|
||||
<div class="flex items-center">
|
||||
<%= render("filters", page: page) %>
|
||||
<div class="ml-3">
|
||||
<%= render(
|
||||
"search",
|
||||
search_term: search_term,
|
||||
resource_name: display_resource_name(page.resource_name)
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if show_search_bar %>
|
||||
<%= render(
|
||||
"search",
|
||||
search_term: search_term,
|
||||
resource_name: display_resource_name(page.resource_name)
|
||||
) %>
|
||||
<% end %>
|
||||
|
||||
<div class="whitespace-nowrap ml-4">
|
||||
<%= link_to(
|
||||
t(
|
||||
"administrate.actions.new_resource",
|
||||
name: page.resource_name.titleize.downcase
|
||||
),
|
||||
[:new, namespace, page.resource_path.to_sym],
|
||||
class: "button",
|
||||
) if accessible_action?(new_resource, :new) %>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<%= link_to(
|
||||
t(
|
||||
"administrate.actions.new_resource",
|
||||
name: page.resource_name.titleize.downcase
|
||||
),
|
||||
[:new, namespace, page.resource_path.to_sym],
|
||||
class: "button",
|
||||
) if accessible_action?(new_resource, :new) %>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -103,16 +103,13 @@
|
||||
display_title: 'Facebook Verify Token'
|
||||
description: 'The verify token used for Facebook Messenger Webhook'
|
||||
locked: false
|
||||
type: secret
|
||||
- name: FB_APP_SECRET
|
||||
display_title: 'Facebook App Secret'
|
||||
locked: false
|
||||
type: secret
|
||||
- name: IG_VERIFY_TOKEN
|
||||
display_title: 'Instagram Verify Token'
|
||||
description: 'The verify token used for Instagram Webhook'
|
||||
locked: false
|
||||
type: secret
|
||||
- name: FACEBOOK_API_VERSION
|
||||
display_title: 'Facebook API Version'
|
||||
description: 'Configure this if you want to use a different Facebook API version. Make sure its prefixed with `v`'
|
||||
@@ -134,7 +131,6 @@
|
||||
- name: AZURE_APP_SECRET
|
||||
display_title: 'Azure App Secret'
|
||||
locked: false
|
||||
type: secret
|
||||
# End of Microsoft Email Channel Config
|
||||
|
||||
# MARK: Captain Config
|
||||
@@ -142,7 +138,6 @@
|
||||
display_title: 'OpenAI API Key'
|
||||
description: 'The API key used to authenticate requests to OpenAI services for Captain AI.'
|
||||
locked: false
|
||||
type: secret
|
||||
- name: CAPTAIN_OPEN_AI_MODEL
|
||||
display_title: 'OpenAI Model'
|
||||
description: 'The OpenAI model configured for use in Captain AI. Default: gpt-4o-mini'
|
||||
@@ -151,7 +146,6 @@
|
||||
display_title: 'FireCrawl API Key (optional)'
|
||||
description: 'The FireCrawl API key for the Captain AI service'
|
||||
locked: false
|
||||
type: secret
|
||||
- name: CAPTAIN_CLOUD_PLAN_LIMITS
|
||||
display_title: 'Captain Cloud Plan Limits'
|
||||
description: 'The limits for the Captain AI service for different plans'
|
||||
@@ -166,13 +160,11 @@
|
||||
display_title: 'Inbox Token'
|
||||
description: 'The Chatwoot Inbox Token for Contact Support in Cloud'
|
||||
locked: false
|
||||
type: secret
|
||||
- name: CHATWOOT_INBOX_HMAC_KEY
|
||||
value:
|
||||
display_title: 'Inbox HMAC Key'
|
||||
description: 'The Chatwoot Inbox HMAC Key for Contact Support in Cloud'
|
||||
locked: false
|
||||
type: secret
|
||||
- name: CHATWOOT_CLOUD_PLANS
|
||||
display_title: 'Cloud Plans'
|
||||
value:
|
||||
@@ -188,12 +180,10 @@
|
||||
value:
|
||||
display_title: 'Analytics Token'
|
||||
description: 'The June.so analytics token for Chatwoot cloud'
|
||||
type: secret
|
||||
- name: CLEARBIT_API_KEY
|
||||
value:
|
||||
display_title: 'Clearbit API Key'
|
||||
description: 'This API key is used for onboarding the users, to pre-fill account data.'
|
||||
type: secret
|
||||
- name: DASHBOARD_SCRIPTS
|
||||
value:
|
||||
display_title: 'Dashboard Scripts'
|
||||
@@ -216,14 +206,12 @@
|
||||
- name: CHATWOOT_SUPPORT_WEBSITE_TOKEN
|
||||
value:
|
||||
description: 'The Chatwoot website token, used to identify the Chatwoot inbox and display the "Contact Support" option on the billing page'
|
||||
type: secret
|
||||
- name: CHATWOOT_SUPPORT_SCRIPT_URL
|
||||
value:
|
||||
description: 'The Chatwoot script base URL, to display the "Contact Support" option on the billing page'
|
||||
- name: CHATWOOT_SUPPORT_IDENTIFIER_HASH
|
||||
value:
|
||||
description: 'The Chatwoot identifier hash, to validate the contact in the live chat window.'
|
||||
type: secret
|
||||
- name: ACCOUNT_SECURITY_NOTIFICATION_WEBHOOK_URL
|
||||
display_title: Webhook URL to post security analysis
|
||||
value:
|
||||
@@ -257,7 +245,6 @@
|
||||
display_title: 'Firebase Credentials'
|
||||
value:
|
||||
locked: false
|
||||
type: secret
|
||||
description: 'Contents on your firebase credentials json file'
|
||||
## ------ End of Configs added for FCM v1 notifications ------ ##
|
||||
|
||||
@@ -272,5 +259,4 @@
|
||||
value:
|
||||
locked: false
|
||||
description: 'Linear client secret'
|
||||
type: secret
|
||||
## ------ End of Configs added for Linear ------ ##
|
||||
|
||||
@@ -68,8 +68,6 @@ am:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reporting period %{since} to %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ ar:
|
||||
invalid_operator: مشغل غير صالح. المشغل المسموح به لـ %{attribute_name} هو [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: قيمة غير صالحة. القيم المقدمة ل %{attribute_name} غير صالحة
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: فترة التبليغ %{since} إلى %{until}
|
||||
utc_warning: التقرير الذي تم إنشاؤه في التوقيت العالمي الموحّد
|
||||
|
||||
@@ -68,8 +68,6 @@ az:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reporting period %{since} to %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ bg:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reporting period %{since} to %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ ca:
|
||||
invalid_operator: Operador no vàlid. Els operadors permesos per a %{attribute_name} son [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Valor no vàlid. Els valors proporcionats per a %{attribute_name} no són vàlids
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Període d'informes %{since} a %{until}
|
||||
utc_warning: L'informe generat es troba a la zona horària UTC
|
||||
|
||||
@@ -68,8 +68,6 @@ cs:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reporting period %{since} to %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ da:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Rapporteringsperiode %{since} til %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ de:
|
||||
invalid_operator: Ungültiger Operator. Die erlaubten Operatoren für %{attribute_name} sind [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Ungültiger Wert. Die Werte für %{attribute_name} sind ungültig
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Berichtszeitraum von %{since} bis %{until}
|
||||
utc_warning: Der generierte Bericht ist in UTC-Zeitzone
|
||||
|
||||
@@ -68,8 +68,6 @@ el:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Περίοδος αναφοράς %{since} έως %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -82,8 +82,6 @@ en:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reporting period %{since} to %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ es:
|
||||
invalid_operator: Operador no válido. Los operadores permitidos para %{attribute_name} son [%{allowed_keys}].
|
||||
invalid_query_operator: El operador de consulta debe ser "Y" o "O".
|
||||
invalid_value: Valor no válido. Los valores proporcionados para %{attribute_name} no son válidos
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reportando el periodo desde %{since} hasta %{until}
|
||||
utc_warning: El informe generado está en zona horaria UTC
|
||||
|
||||
@@ -68,8 +68,6 @@ fa:
|
||||
invalid_operator: این عملیات مجاز نیست. عملیات های مجاز برای %{attribute_name} شامل %{allowed_keys} می باشد.
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: مقدار معتبر نیست. مقادیر ارائه شده برای %{attribute_name} معتبر نیست
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: زمان گزارش از %{since} تا %{until}
|
||||
utc_warning: گزارش تولید شده در منطقه زمانی UTC است
|
||||
|
||||
@@ -68,8 +68,6 @@ fi:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Raportointijakso %{since} – %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ fr:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Période de rapport %{since} à %{until}
|
||||
utc_warning: Le rapport généré est dans le fuseau horaire UTC
|
||||
|
||||
@@ -68,8 +68,6 @@ he:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reporting period %{since} to %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ hi:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reporting period %{since} to %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ hr:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reporting period %{since} to %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ hu:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Jelentési időszak %{since}-tól %{until}-ig
|
||||
utc_warning: A generált riport UTC időzónát használ
|
||||
|
||||
@@ -68,8 +68,6 @@ hy:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reporting period %{since} to %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ id:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Periode pelaporan %{since} hingga %{until}
|
||||
utc_warning: Laporan yang dihasilkan berada dalam zona waktu UTC
|
||||
|
||||
@@ -68,8 +68,6 @@ is:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reporting period %{since} to %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ it:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Periodo di segnalazione da %{since} a %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ ja:
|
||||
invalid_operator: 無効な演算子です。%{attribute_name} に許可されている演算子は [%{allowed_keys}] です。
|
||||
invalid_query_operator: クエリ演算子は "AND" または "OR" でなければなりません。
|
||||
invalid_value: 無効な値です。%{attribute_name} に提供された値は無効です。
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: レポート期間 %{since} から %{until} まで
|
||||
utc_warning: 生成されたレポートはUTCタイムゾーンです
|
||||
|
||||
@@ -68,8 +68,6 @@ ka:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reporting period %{since} to %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ ko:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: 보고 기간 %{since} - %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ lt:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Ataskaitinis laikotarpis nuo %{since} iki %{until}
|
||||
utc_warning: Sugeneruota ataskaita yra UTC laiko juostoje
|
||||
|
||||
@@ -68,8 +68,6 @@ lv:
|
||||
invalid_operator: Nederīgs operators. Atļautie operatori priekš %{attribute_name} ir [%{allowed_keys}].
|
||||
invalid_query_operator: Vaicājuma operatoram ir jābūt "UN" vai "VAI".
|
||||
invalid_value: Nederīga vērtība. Norādītās vērtības priekš %{attribute_name} nav derīgas
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Ziņošanas periods %{since} līdz %{until}
|
||||
utc_warning: Izveidotais pārskats atbilst UTC laika joslai
|
||||
|
||||
@@ -68,8 +68,6 @@ ml:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reporting period %{since} to %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ ms:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reporting period %{since} to %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ ne:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reporting period %{since} to %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ nl:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Rapportering van %{since} tot %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@
|
||||
invalid_operator: Ugyldig operatør. De tillatte operatørene for %{attribute_name} er [%{allowed_keys}].
|
||||
invalid_query_operator: Spørrings-operatør må være enten "AND" eller "OR".
|
||||
invalid_value: Ugyldig verdi. Verdiene angitt for %{attribute_name} er ugyldige
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Rapporteringsperiode %{since} til %{until}
|
||||
utc_warning: Rapporten generert er i UTC tidssone
|
||||
|
||||
@@ -68,8 +68,6 @@ pl:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Okres raportowania od %{since} do %{until}
|
||||
utc_warning: Generowany raport jest w strefie czasowej UTC
|
||||
|
||||
@@ -68,8 +68,6 @@ pt:
|
||||
invalid_operator: Operador inválido. Os operadores permitidos para %{attribute_name} são [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Valor inválido. Os valores fornecidos para %{attribute_name} são inválidos
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Período do relatório de %{since} a %{until}
|
||||
utc_warning: O relatório gerado está no fuso horário UTC
|
||||
|
||||
@@ -68,8 +68,6 @@ pt_BR:
|
||||
invalid_operator: Operador inválido. Os operadores permitidos para %{attribute_name} são [%{allowed_keys}].
|
||||
invalid_query_operator: Operador de consulta deve ser "E" ou "OU".
|
||||
invalid_value: Valor inválido. Os valores fornecidos para %{attribute_name} são inválidos
|
||||
custom_attribute_definition:
|
||||
key_conflict: A chave fornecida não é permitida pois pode entrar em conflito com os atributos padrão.
|
||||
reports:
|
||||
period: Reportando o período %{since} a %{until}
|
||||
utc_warning: O relatório gerado está em fuso horário UTC
|
||||
@@ -135,7 +133,7 @@ pt_BR:
|
||||
messages:
|
||||
instagram_story_content: '%{story_sender} mencionou você na conversa: '
|
||||
instagram_deleted_story_content: Este Story não está mais disponível.
|
||||
deleted: Esta mensagem foi excluída
|
||||
deleted: Esta mensagem foi apagada
|
||||
delivery_status:
|
||||
error_code: 'Código de erro: %{error_code}'
|
||||
activity:
|
||||
|
||||
@@ -68,8 +68,6 @@ ro:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Perioada de raportare %{since}-%{until}
|
||||
utc_warning: Raportul generat este în fusul orar UTC
|
||||
|
||||
@@ -68,8 +68,6 @@ ru:
|
||||
invalid_operator: Неверный оператор. Допустимыми операторами для %{attribute_name} являются [%{allowed_keys}].
|
||||
invalid_query_operator: Оператор запроса должен быть "AND" или "OR".
|
||||
invalid_value: Недопустимое значение. Значения, предоставленные для %{attribute_name} являются недопустимыми
|
||||
custom_attribute_definition:
|
||||
key_conflict: Предоставленный ключ не разрешён, так как он может конфликтовать со стандартными атрибутами.
|
||||
reports:
|
||||
period: Отчётный период с %{since} по %{until}
|
||||
utc_warning: Отчёт создан в часовом поясе UTC
|
||||
@@ -167,7 +165,7 @@ ru:
|
||||
removed: '%{user_name} удалил политику SLA %{sla_name}'
|
||||
muted: '%{user_name} заглушил(а) этот разговор'
|
||||
unmuted: '%{user_name} включил(а) уведомления для разговора'
|
||||
auto_resolution_message: 'Разговор закрывается, поскольку он был неактивен в течение длительного времени. Пожалуйста, начните новый разговор, если потребуется дополнительная помощь.'
|
||||
auto_resolution_message: 'Resolving the conversation as it has been inactive for a while. Please start a new conversation if you need further assistance.'
|
||||
templates:
|
||||
greeting_message_body: '%{account_name} как правило отвечает в течении несколько часов.'
|
||||
ways_to_reach_you_message_body: 'Оставьте ваш email для связи'
|
||||
@@ -218,8 +216,8 @@ ru:
|
||||
name: 'Linear'
|
||||
description: 'Создавайте или прикрепляйте уже существующие задачи в Linear непосредственно из окна диалога для более упорядоченного и эффективного процесса отслеживания проблем.'
|
||||
captain:
|
||||
copilot_error: 'Пожалуйста, подключите ассистента к этому источнику входящих для использования Copilot'
|
||||
copilot_limit: 'У вас закончились кредиты для Copilot. Вы можете купить дополнительные кредиты в разделе биллинга.'
|
||||
copilot_error: 'Please connect an assistant to this inbox to use Copilot'
|
||||
copilot_limit: 'You are out of Copilot credits. You can buy more credits from the billing section.'
|
||||
public_portal:
|
||||
search:
|
||||
search_placeholder: Поиск статьи по названию или содержанию...
|
||||
|
||||
@@ -68,8 +68,6 @@ sh:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reporting period %{since} to %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ sk:
|
||||
invalid_operator: Invalid operator. The allowed operators for %{attribute_name} are [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Invalid value. The values provided for %{attribute_name} are invalid
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Reporting period %{since} to %{until}
|
||||
utc_warning: The report generated is in UTC timezone
|
||||
|
||||
@@ -68,8 +68,6 @@ sl:
|
||||
invalid_operator: Neveljaven operater. Dovoljeni operaterji za %{attribute_name} so [%{allowed_keys}].
|
||||
invalid_query_operator: Query operator must be either "AND" or "OR".
|
||||
invalid_value: Neveljavna vrednost. Podane vrednosti za %{attribute_name} so neveljavne
|
||||
custom_attribute_definition:
|
||||
key_conflict: The provided key is not allowed as it might conflict with default attributes.
|
||||
reports:
|
||||
period: Obdobje poročanja %{since} do %{until}
|
||||
utc_warning: Ustvarjeno poročilo je v časovnem pasu UTC
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user