Merge branch 'develop' into codex/linear-mention-cw-5548-cannot-create-a-linear-ticket

This commit is contained in:
Muhsin Keloth
2026-01-12 11:35:50 +04:00
committed by GitHub
462 changed files with 10746 additions and 1562 deletions
+44
View File
@@ -218,6 +218,49 @@ jobs:
source ~/.rvm/scripts/rvm
bundle install
# Install and configure OpenSearch
- run:
name: Install OpenSearch
command: |
# Download and install OpenSearch 2.11.0 (compatible with Elasticsearch 7.x clients)
wget https://artifacts.opensearch.org/releases/bundle/opensearch/2.11.0/opensearch-2.11.0-linux-x64.tar.gz
tar -xzf opensearch-2.11.0-linux-x64.tar.gz
sudo mv opensearch-2.11.0 /opt/opensearch
- run:
name: Configure and Start OpenSearch
command: |
# Configure OpenSearch for single-node testing
cat > /opt/opensearch/config/opensearch.yml \<< EOF
cluster.name: chatwoot-test
node.name: node-1
network.host: 0.0.0.0
http.port: 9200
discovery.type: single-node
plugins.security.disabled: true
EOF
# Set ownership and permissions
sudo chown -R $USER:$USER /opt/opensearch
# Start OpenSearch in background
/opt/opensearch/bin/opensearch -d -p /tmp/opensearch.pid
- run:
name: Wait for OpenSearch to be ready
command: |
echo "Waiting for OpenSearch to start..."
for i in {1..30}; do
if curl -s http://localhost:9200/_cluster/health | grep -q '"status"'; then
echo "OpenSearch is ready!"
exit 0
fi
echo "Waiting... ($i/30)"
sleep 2
done
echo "OpenSearch failed to start"
exit 1
# Configure environment and database
- run:
name: Database Setup and Configure Environment Variables
@@ -234,6 +277,7 @@ jobs:
sed -i -e '/POSTGRES_USERNAME/ s/=.*/=chatwoot/' .env
sed -i -e "/POSTGRES_PASSWORD/ s/=.*/=$pg_pass/" .env
echo -en "\nINSTALLATION_ENV=circleci" >> ".env"
echo -en "\nOPENSEARCH_URL=http://localhost:9200" >> ".env"
# Database setup
- run:
+4 -2
View File
@@ -441,7 +441,8 @@ GEM
http-cookie (1.0.5)
domain_name (~> 0.5)
http-form_data (2.3.0)
httparty (0.21.0)
httparty (0.24.0)
csv
mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
@@ -555,7 +556,8 @@ GEM
ruby2_keywords
msgpack (1.8.0)
multi_json (1.15.0)
multi_xml (0.6.0)
multi_xml (0.8.0)
bigdecimal (>= 3.1, < 5)
multipart-post (2.3.0)
mutex_m (0.3.0)
neighbor (0.2.3)
+1 -2
View File
@@ -8,7 +8,6 @@ ___
The modern customer support platform, an open-source alternative to Intercom, Zendesk, Salesforce Service Cloud etc.
<p>
<a href="https://codeclimate.com/github/chatwoot/chatwoot/maintainability"><img src="https://api.codeclimate.com/v1/badges/e6e3f66332c91e5a4c0c/maintainability" alt="Maintainability"></a>
<img src="https://img.shields.io/circleci/build/github/chatwoot/chatwoot" alt="CircleCI Badge">
<a href="https://hub.docker.com/r/chatwoot/chatwoot/"><img src="https://img.shields.io/docker/pulls/chatwoot/chatwoot" alt="Docker Pull Badge"></a>
<a href="https://hub.docker.com/r/chatwoot/chatwoot/"><img src="https://img.shields.io/docker/cloud/build/chatwoot/chatwoot" alt="Docker Build Badge"></a>
@@ -137,4 +136,4 @@ Thanks goes to all these [wonderful people](https://www.chatwoot.com/docs/contri
<a href="https://github.com/chatwoot/chatwoot/graphs/contributors"><img src="https://opencollective.com/chatwoot/contributors.svg?width=890&button=false" /></a>
*Chatwoot* &copy; 2017-2025, Chatwoot Inc - Released under the MIT License.
*Chatwoot* &copy; 2017-2026, Chatwoot Inc - Released under the MIT License.
@@ -28,5 +28,7 @@ class Api::V1::Accounts::SearchController < Api::V1::Accounts::BaseController
search_type: search_type,
params: params
).perform
rescue ArgumentError => e
render json: { error: e.message }, status: :unprocessable_entity
end
end
+1 -1
View File
@@ -16,7 +16,7 @@ class DashboardController < ActionController::Base
CHATWOOT_INBOX_TOKEN
API_CHANNEL_NAME
API_CHANNEL_THUMBNAIL
ANALYTICS_TOKEN
CLOUD_ANALYTICS_TOKEN
DIRECT_UPLOADS_ENABLED
MAXIMUM_FILE_UPLOAD_SIZE
HCAPTCHA_SITE_KEY
+10
View File
@@ -32,6 +32,16 @@ class Inboxes extends CacheEnabledApiClient {
syncTemplates(inboxId) {
return axios.post(`${this.url}/${inboxId}/sync_templates`);
}
createCSATTemplate(inboxId, template) {
return axios.post(`${this.url}/${inboxId}/csat_template`, {
template,
});
}
getCSATTemplateStatus(inboxId) {
return axios.get(`${this.url}/${inboxId}/csat_template`);
}
}
export default new Inboxes();
+14 -4
View File
@@ -14,38 +14,48 @@ class SearchAPI extends ApiClient {
});
}
contacts({ q, page = 1 }) {
contacts({ q, page = 1, since, until }) {
return axios.get(`${this.url}/contacts`, {
params: {
q,
page: page,
since,
until,
},
});
}
conversations({ q, page = 1 }) {
conversations({ q, page = 1, since, until }) {
return axios.get(`${this.url}/conversations`, {
params: {
q,
page: page,
since,
until,
},
});
}
messages({ q, page = 1 }) {
messages({ q, page = 1, since, until, from, inboxId }) {
return axios.get(`${this.url}/messages`, {
params: {
q,
page: page,
since,
until,
from,
inbox_id: inboxId,
},
});
}
articles({ q, page = 1 }) {
articles({ q, page = 1, since, until }) {
return axios.get(`${this.url}/articles`, {
params: {
q,
page: page,
since,
until,
},
});
}
@@ -0,0 +1,134 @@
import searchAPI from '../search';
import ApiClient from '../ApiClient';
describe('#SearchAPI', () => {
it('creates correct instance', () => {
expect(searchAPI).toBeInstanceOf(ApiClient);
expect(searchAPI).toHaveProperty('get');
expect(searchAPI).toHaveProperty('contacts');
expect(searchAPI).toHaveProperty('conversations');
expect(searchAPI).toHaveProperty('messages');
expect(searchAPI).toHaveProperty('articles');
});
describe('API calls', () => {
const originalAxios = window.axios;
const axiosMock = {
get: vi.fn(() => Promise.resolve()),
};
beforeEach(() => {
window.axios = axiosMock;
});
afterEach(() => {
window.axios = originalAxios;
vi.clearAllMocks();
});
it('#get', () => {
searchAPI.get({ q: 'test query' });
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/search', {
params: { q: 'test query' },
});
});
it('#contacts', () => {
searchAPI.contacts({ q: 'test', page: 1 });
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/search/contacts', {
params: { q: 'test', page: 1, since: undefined, until: undefined },
});
});
it('#contacts with date filters', () => {
searchAPI.contacts({
q: 'test',
page: 2,
since: 1700000000,
until: 1732000000,
});
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/search/contacts', {
params: { q: 'test', page: 2, since: 1700000000, until: 1732000000 },
});
});
it('#conversations', () => {
searchAPI.conversations({ q: 'test', page: 1 });
expect(axiosMock.get).toHaveBeenCalledWith(
'/api/v1/search/conversations',
{
params: { q: 'test', page: 1, since: undefined, until: undefined },
}
);
});
it('#conversations with date filters', () => {
searchAPI.conversations({
q: 'test',
page: 1,
since: 1700000000,
until: 1732000000,
});
expect(axiosMock.get).toHaveBeenCalledWith(
'/api/v1/search/conversations',
{
params: { q: 'test', page: 1, since: 1700000000, until: 1732000000 },
}
);
});
it('#messages', () => {
searchAPI.messages({ q: 'test', page: 1 });
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/search/messages', {
params: {
q: 'test',
page: 1,
since: undefined,
until: undefined,
from: undefined,
inbox_id: undefined,
},
});
});
it('#messages with all filters', () => {
searchAPI.messages({
q: 'test',
page: 1,
since: 1700000000,
until: 1732000000,
from: 'contact:42',
inboxId: 10,
});
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/search/messages', {
params: {
q: 'test',
page: 1,
since: 1700000000,
until: 1732000000,
from: 'contact:42',
inbox_id: 10,
},
});
});
it('#articles', () => {
searchAPI.articles({ q: 'test', page: 1 });
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/search/articles', {
params: { q: 'test', page: 1, since: undefined, until: undefined },
});
});
it('#articles with date filters', () => {
searchAPI.articles({
q: 'test',
page: 2,
since: 1700000000,
until: 1732000000,
});
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/search/articles', {
params: { q: 'test', page: 2, since: 1700000000, until: 1732000000 },
});
});
});
});
@@ -19,7 +19,7 @@ const handleClick = () => {
<template>
<div
class="flex flex-col w-full shadow outline-1 outline outline-n-container group/cardLayout rounded-2xl bg-n-solid-2"
class="flex flex-col w-full outline-1 outline outline-n-container group/cardLayout rounded-xl bg-n-solid-2"
>
<div
class="flex w-full gap-3 py-5"
@@ -35,6 +35,10 @@ const sortMenus = [
label: t('COMPANIES.SORT_BY.OPTIONS.CREATED_AT'),
value: 'created_at',
},
{
label: t('COMPANIES.SORT_BY.OPTIONS.CONTACTS_COUNT'),
value: 'contacts_count',
},
];
const orderingMenus = [
@@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
import Icon from 'dashboard/components-next/icon/Icon.vue';
import Avatar from 'dashboard/components-next/avatar/Avatar.vue';
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
const props = defineProps({
menuItems: {
@@ -37,9 +38,13 @@ const props = defineProps({
type: String,
default: '',
},
disableLocalFiltering: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['action']);
const emit = defineEmits(['action', 'search']);
const { t } = useI18n();
@@ -57,6 +62,7 @@ const flattenedMenuItems = computed(() => {
});
const filteredMenuItems = computed(() => {
if (props.disableLocalFiltering) return props.menuItems;
if (!searchQuery.value) return flattenedMenuItems.value;
return flattenedMenuItems.value.filter(item =>
@@ -69,7 +75,7 @@ const filteredMenuSections = computed(() => {
return [];
}
if (!searchQuery.value) {
if (props.disableLocalFiltering || !searchQuery.value) {
return props.menuSections;
}
@@ -89,6 +95,12 @@ const filteredMenuSections = computed(() => {
.filter(section => section.items.length > 0);
});
const handleSearchInput = event => {
if (props.disableLocalFiltering) {
emit('search', event.target.value);
}
};
const handleAction = item => {
const { action, value, ...rest } = item;
emit('action', { action, value, ...rest });
@@ -118,7 +130,7 @@ onMounted(() => {
>
<div
v-if="showSearch"
class="sticky top-0 bg-n-alpha-3 backdrop-blur-sm pt-2"
class="sticky top-0 bg-n-alpha-3 backdrop-blur-sm pt-2 z-20"
>
<div class="relative">
<span class="absolute i-lucide-search size-3.5 top-2 left-3" />
@@ -130,6 +142,7 @@ onMounted(() => {
searchPlaceholder || t('DROPDOWN_MENU.SEARCH_PLACEHOLDER')
"
class="reset-base w-full h-8 py-2 pl-10 pr-2 text-sm focus:outline-none border-none rounded-lg bg-n-alpha-black2 dark:bg-n-solid-1 text-n-slate-12"
@input="handleSearchInput"
/>
</div>
</div>
@@ -141,10 +154,23 @@ onMounted(() => {
>
<p
v-if="section.title"
class="px-2 pt-2 text-xs font-medium text-n-slate-11 uppercase tracking-wide"
class="px-2 py-2 text-xs mb-0 font-medium text-n-slate-11 uppercase tracking-wide sticky z-10 bg-n-alpha-3 backdrop-blur-sm"
:class="showSearch ? 'top-10' : 'top-0'"
>
{{ section.title }}
</p>
<div
v-if="section.isLoading"
class="flex items-center justify-center py-2"
>
<Spinner :size="24" />
</div>
<div
v-else-if="!section.items.length && section.emptyState"
class="text-sm text-n-slate-11 px-2 py-1.5"
>
{{ section.emptyState }}
</div>
<button
v-for="(item, itemIndex) in section.items"
:key="item.value || itemIndex"
@@ -235,5 +261,6 @@ onMounted(() => {
: t('DROPDOWN_MENU.EMPTY_STATE')
}}
</div>
<slot name="footer" />
</div>
</template>
@@ -42,7 +42,10 @@ const props = defineProps({
const emit = defineEmits(['retry']);
const allMessages = computed(() => {
return useCamelCase(props.messages, { deep: true });
return useCamelCase(props.messages, {
deep: true,
stopPaths: ['content_attributes.translations'],
});
});
const currentChat = useMapGetter('getSelectedChat');
@@ -0,0 +1,28 @@
<script setup>
import Button from 'dashboard/components-next/button/Button.vue';
defineProps({
message: {
type: Object,
required: true,
},
buttonText: {
type: String,
required: true,
},
});
</script>
<template>
<div class="flex flex-col gap-2.5 text-n-slate-12 max-w-80">
<div class="p-3 rounded-xl bg-n-alpha-2">
<span
v-dompurify-html="message.content"
class="text-sm font-medium prose prose-bubble"
/>
</div>
<div class="flex gap-2">
<Button :label="buttonText" slate class="!text-n-blue-text w-full" />
</div>
</div>
</template>
@@ -17,6 +17,10 @@ const { attachment } = defineProps({
type: Object,
required: true,
},
showTranscribedText: {
type: Boolean,
default: true,
},
});
defineOptions({
@@ -182,7 +186,7 @@ const downloadAudio = async () => {
</div>
<div
v-if="attachment.transcribedText"
v-if="attachment.transcribedText && showTranscribedText"
class="text-n-slate-12 p-3 text-sm bg-n-alpha-1 rounded-lg w-full break-words"
>
{{ attachment.transcribedText }}
@@ -1,12 +1,11 @@
<script setup>
import { h, computed, onMounted } from 'vue';
import { h, ref, computed, onMounted } from 'vue';
import { provideSidebarContext } from './provider';
import { useAccount } from 'dashboard/composables/useAccount';
import { useKbd } from 'dashboard/composables/utils/useKbd';
import { useMapGetter } from 'dashboard/composables/store';
import { useStore } from 'vuex';
import { useI18n } from 'vue-i18n';
import { useStorage } from '@vueuse/core';
import { useSidebarKeyboardShortcuts } from './useSidebarKeyboardShortcuts';
import { vOnClickOutside } from '@vueuse/components';
import { emitter } from 'shared/helpers/mitt';
@@ -55,14 +54,7 @@ const toggleShortcutModalFn = show => {
useSidebarKeyboardShortcuts(toggleShortcutModalFn);
// We're using localStorage to store the expanded item in the sidebar
// This helps preserve context when navigating between portal and dashboard layouts
// and also when the user refreshes the page
const expandedItem = useStorage(
'next-sidebar-expanded-item',
null,
sessionStorage
);
const expandedItem = ref(null);
const setExpandedItem = name => {
expandedItem.value = expandedItem.value === name ? null : name;
@@ -1,5 +1,5 @@
<script setup>
import { computed, onMounted, nextTick } from 'vue';
import { computed, onMounted, watch, nextTick } from 'vue';
import { useSidebarContext } from './provider';
import { useRoute, useRouter } from 'vue-router';
import Policy from 'dashboard/components/policy.vue';
@@ -126,6 +126,16 @@ onMounted(async () => {
setExpandedItem(props.name);
}
});
watch(
hasActiveChild,
hasNewActiveChild => {
if (hasNewActiveChild && !isExpanded.value) {
setExpandedItem(props.name);
}
},
{ once: true }
);
</script>
<!-- eslint-disable-next-line vue/no-root-v-if -->
@@ -1,5 +1,5 @@
<script setup>
import { computed, ref, onMounted, nextTick } from 'vue';
import { computed, ref, onMounted, nextTick, watch } from 'vue';
import { useResizeObserver } from '@vueuse/core';
const props = defineProps({
@@ -31,20 +31,24 @@ const enableTransition = ref(false);
const activeElement = computed(() => tabRefs.value[activeTab.value]);
const updateIndicator = () => {
if (!activeElement.value) return;
nextTick(() => {
if (!activeElement.value) return;
indicatorStyle.value = {
left: `${activeElement.value.offsetLeft}px`,
width: `${activeElement.value.offsetWidth}px`,
};
indicatorStyle.value = {
left: `${activeElement.value.offsetLeft}px`,
width: `${activeElement.value.offsetWidth}px`,
};
});
};
useResizeObserver(activeElement, () => {
if (enableTransition.value || !activeElement.value) updateIndicator();
useResizeObserver(activeElement, updateIndicator);
// Watch for prop/tabs changes to update indicator position
watch([() => props.initialActiveTab, () => props.tabs], updateIndicator, {
immediate: true,
});
onMounted(() => {
updateIndicator();
nextTick(() => {
enableTransition.value = true;
});
@@ -66,7 +70,7 @@ const showDivider = index => {
<template>
<div
class="relative flex items-center h-8 rounded-lg bg-n-alpha-1 w-fit transition-all duration-200 ease-out has-[button:active]:scale-[1.01]"
class="relative flex items-center h-8 rounded-lg bg-n-alpha-1 dark:bg-n-solid-1 w-fit transition-all duration-200 ease-out has-[button:active]:scale-[1.01]"
>
<div
class="absolute rounded-lg bg-n-solid-active shadow-sm pointer-events-none h-8 outline-1 outline outline-n-container inset-y-0"
@@ -55,6 +55,7 @@ import {
getSelectionCoords,
calculateMenuPosition,
getEffectiveChannelType,
stripUnsupportedFormatting,
} from 'dashboard/helper/editorHelper';
import {
hasPressedEnterAndNotCmdOrShift,
@@ -131,8 +132,10 @@ const editorMenuOptions = computed(() => {
const createState = (content, placeholder, plugins = [], methods = {}) => {
const schema = editorSchema.value;
// Strip unsupported formatting before parsing to prevent "Token type not supported" errors
const sanitizedContent = stripUnsupportedFormatting(content, schema);
return EditorState.create({
doc: new MessageMarkdownTransformer(schema).parse(content),
doc: new MessageMarkdownTransformer(schema).parse(sanitizedContent),
plugins: buildEditor({
schema,
placeholder,
@@ -859,6 +862,7 @@ useEmitter(BUS_EVENTS.INSERT_INTO_RICH_EDITOR, insertContentIntoEditor);
max-height: none !important;
min-height: 0 !important;
padding: 0 !important;
display: none !important;
}
> .ProseMirror {
@@ -7,9 +7,7 @@ import { useTrack } from 'dashboard/composables';
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
import CannedResponse from './CannedResponse.vue';
import ReplyToMessage from './ReplyToMessage.vue';
import ResizableTextArea from 'shared/components/ResizableTextArea.vue';
import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview.vue';
import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel.vue';
import ReplyEmailHead from './ReplyEmailHead.vue';
@@ -46,7 +44,6 @@ import {
appendSignature,
removeSignature,
getEffectiveChannelType,
extractTextFromMarkdown,
} from 'dashboard/helper/editorHelper';
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
@@ -72,8 +69,6 @@ export default {
WhatsappTemplates,
WootMessageEditor,
QuotedEmailPreview,
ResizableTextArea,
CannedResponse,
},
mixins: [inboxMixin, fileUploadMixin, keyboardEventListenerMixins],
props: {
@@ -114,8 +109,6 @@ export default {
recordingAudioState: '',
recordingAudioDurationText: '',
replyType: REPLY_EDITOR_MODES.REPLY,
mentionSearchKey: '',
hasSlashCommand: false,
bccEmails: '',
ccEmails: '',
toEmails: '',
@@ -148,9 +141,6 @@ export default {
if (!senderId) return {};
return this.$store.getters['contacts/getContact'](senderId);
},
isRichEditorEnabled() {
return this.isAWebWidgetInbox || this.isAnEmailChannel || this.isAPIInbox;
},
shouldShowReplyToMessage() {
return (
this.inReplyTo?.id &&
@@ -409,19 +399,6 @@ export default {
!!this.quotedEmailText
);
},
showRichContentEditor() {
if (this.isOnPrivateNote || this.isRichEditorEnabled) {
return true;
}
return false;
},
// ensure that the signature is plain text depending on `showRichContentEditor`
signatureToApply() {
return this.showRichContentEditor
? this.messageSignature
: extractTextFromMarkdown(this.messageSignature);
},
},
watch: {
currentChat(conversation, oldConversation) {
@@ -464,25 +441,7 @@ export default {
this.resetRecorderAndClearAttachments();
}
},
message(updatedMessage) {
// Check if the message starts with a slash.
const bodyWithoutSignature = removeSignature(
updatedMessage,
this.signatureToApply
);
const startsWithSlash = bodyWithoutSignature.startsWith('/');
// Determine if the user is potentially typing a slash command.
// This is true if the message starts with a slash and the rich content editor is not active.
this.hasSlashCommand = startsWithSlash && !this.showRichContentEditor;
this.showMentions = this.hasSlashCommand;
// If a slash command is active, extract the command text after the slash.
// If not, reset the mentionSearchKey.
this.mentionSearchKey = this.hasSlashCommand
? bodyWithoutSignature.substring(1)
: '';
message() {
// Autosave the current message draft.
this.doAutoSaveDraft();
},
@@ -532,20 +491,14 @@ export default {
methods: {
handleInsert(article) {
const { url, title } = article;
if (this.isRichEditorEnabled) {
// Removing empty lines from the title
const lines = title.split('\n');
const nonEmptyLines = lines.filter(line => line.trim() !== '');
const filteredMarkdown = nonEmptyLines.join(' ');
emitter.emit(
BUS_EVENTS.INSERT_INTO_RICH_EDITOR,
`[${filteredMarkdown}](${url})`
);
} else {
this.addIntoEditor(
`${this.$t('CONVERSATION.REPLYBOX.INSERT_READ_MORE')} ${url}`
);
}
// Removing empty lines from the title
const lines = title.split('\n');
const nonEmptyLines = lines.filter(line => line.trim() !== '');
const filteredMarkdown = nonEmptyLines.join(' ');
emitter.emit(
BUS_EVENTS.INSERT_INTO_RICH_EDITOR,
`[${filteredMarkdown}](${url})`
);
useTrack(CONVERSATION_EVENTS.INSERT_ARTICLE_LINK);
},
@@ -614,26 +567,14 @@ export default {
if (this.isPrivate) {
return message;
}
if (this.showRichContentEditor) {
const effectiveChannelType = getEffectiveChannelType(
this.channelType,
this.inbox?.medium || ''
);
return this.sendWithSignature
? appendSignature(
message,
this.messageSignature,
effectiveChannelType
)
: removeSignature(
message,
this.messageSignature,
effectiveChannelType
);
}
const effectiveChannelType = getEffectiveChannelType(
this.channelType,
this.inbox?.medium || ''
);
return this.sendWithSignature
? appendSignature(message, this.signatureToApply)
: removeSignature(message, this.signatureToApply);
? appendSignature(message, this.messageSignature, effectiveChannelType)
: removeSignature(message, this.messageSignature, effectiveChannelType);
},
removeFromDraft() {
if (this.conversationIdByRoute) {
@@ -649,7 +590,6 @@ export default {
Escape: {
action: () => {
this.hideEmojiPicker();
this.hideMentions();
},
allowOnFocusedInput: true,
},
@@ -694,11 +634,6 @@ export default {
// Don't handle paste if compose new conversation modal is open
if (this.newConversationModalActive) return;
const data = e.clipboardData.files;
if (!this.showRichContentEditor && data.length !== 0) {
this.$refs.messageInput?.$el?.blur();
}
// Filter valid files (non-zero size)
Array.from(e.clipboardData.files)
.filter(file => file.size > 0)
@@ -832,19 +767,15 @@ export default {
// if signature is enabled, append it to the message
// appendSignature ensures that the signature is not duplicated
// so we don't need to check if the signature is already present
if (this.showRichContentEditor) {
const effectiveChannelType = getEffectiveChannelType(
this.channelType,
this.inbox?.medium || ''
);
message = appendSignature(
message,
this.messageSignature,
effectiveChannelType
);
} else {
message = appendSignature(message, this.signatureToApply);
}
const effectiveChannelType = getEffectiveChannelType(
this.channelType,
this.inbox?.medium || ''
);
message = appendSignature(
message,
this.messageSignature,
effectiveChannelType
);
}
const updatedMessage = replaceVariablesInMessage({
@@ -868,52 +799,30 @@ export default {
});
if (canReply || this.isAWhatsAppChannel || this.isAPIInbox)
this.replyType = mode;
if (this.showRichContentEditor) {
if (this.isRecordingAudio) {
this.toggleAudioRecorder();
}
return;
if (this.isRecordingAudio) {
this.toggleAudioRecorder();
}
this.$nextTick(() => this.$refs.messageInput.focus());
},
clearEditorSelection() {
this.updateEditorSelectionWith = '';
},
insertIntoTextEditor(text, selectionStart, selectionEnd) {
const { message } = this;
const newMessage =
message.slice(0, selectionStart) +
text +
message.slice(selectionEnd, message.length);
this.message = newMessage;
},
addIntoEditor(content) {
if (this.showRichContentEditor) {
this.updateEditorSelectionWith = content;
this.onFocus();
}
if (!this.showRichContentEditor) {
const { selectionStart, selectionEnd } = this.$refs.messageInput.$el;
this.insertIntoTextEditor(content, selectionStart, selectionEnd);
}
this.updateEditorSelectionWith = content;
this.onFocus();
},
clearMessage() {
this.message = '';
if (this.sendWithSignature && !this.isPrivate) {
// if signature is enabled, append it to the message
if (this.showRichContentEditor) {
const effectiveChannelType = getEffectiveChannelType(
this.channelType,
this.inbox?.medium || ''
);
this.message = appendSignature(
this.message,
this.messageSignature,
effectiveChannelType
);
} else {
this.message = appendSignature(this.message, this.signatureToApply);
}
const effectiveChannelType = getEffectiveChannelType(
this.channelType,
this.inbox?.medium || ''
);
this.message = appendSignature(
this.message,
this.messageSignature,
effectiveChannelType
);
}
this.attachedFiles = [];
this.isRecordingAudio = false;
@@ -948,9 +857,6 @@ export default {
this.toggleEmojiPicker();
}
},
hideMentions() {
this.showMentions = false;
},
onTypingOn() {
this.toggleTyping('on');
},
@@ -1197,13 +1103,6 @@ export default {
:message="inReplyTo"
@dismiss="resetReplyToMessage"
/>
<CannedResponse
v-if="showMentions && hasSlashCommand"
v-on-clickaway="hideMentions"
class="normal-editor__canned-box"
:search-key="mentionSearchKey"
@replace="replaceText"
/>
<EmojiInput
v-if="showEmojiPicker"
v-on-clickaway="hideEmojiPicker"
@@ -1227,23 +1126,7 @@ export default {
@play="recordingAudioState = 'playing'"
@pause="recordingAudioState = 'paused'"
/>
<ResizableTextArea
v-else-if="!showRichContentEditor"
ref="messageInput"
v-model="message"
class="rounded-none input"
:placeholder="messagePlaceHolder"
:min-height="4"
:signature="signatureToApply"
allow-signature
:send-with-signature="sendWithSignature"
@typing-off="onTypingOff"
@typing-on="onTypingOn"
@focus="onFocus"
@blur="onBlur"
/>
<WootMessageEditor
v-else
v-model="message"
:editor-id="editorStateId"
class="input popover-prosemirror-menu"
@@ -1369,10 +1252,6 @@ export default {
.reply-box__top {
@apply relative py-0 px-4 -mt-px;
textarea {
@apply shadow-none outline-none border-transparent bg-transparent m-0 max-h-60 min-h-[3rem] pt-4 pb-0 px-0 resize-none;
}
}
.emoji-dialog {
@@ -1392,9 +1271,4 @@ export default {
@apply ltr:left-1 rtl:right-1 -bottom-2;
}
}
.normal-editor__canned-box {
width: calc(100% - 2 * 1rem);
left: 1rem;
}
</style>
@@ -1,6 +1,7 @@
<script setup>
import { ref, watch, computed, nextTick } from 'vue';
import { useKeyboardNavigableList } from 'dashboard/composables/useKeyboardNavigableList';
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
const props = defineProps({
items: {
@@ -15,6 +16,8 @@ const props = defineProps({
const emit = defineEmits(['mentionSelect']);
const { getPlainText } = useMessageFormatter();
const mentionsListContainerRef = ref(null);
const selectedIndex = ref(0);
@@ -94,7 +97,7 @@ const variableKey = (item = {}) => {
'text-n-slate-12': index === selectedIndex,
}"
>
{{ item.description }}
{{ getPlainText(item.description) }}
</p>
<p
class="max-w-full min-w-0 mb-0 overflow-hidden text-xs text-n-slate-11 group-hover:text-n-slate-12 text-ellipsis whitespace-nowrap"
@@ -1,39 +1,31 @@
import { ref } from 'vue';
import { useTranslations } from '../useTranslations';
import { selectTranslation } from '../useTranslations';
describe('useTranslations', () => {
it('returns false and null when contentAttributes is null', () => {
const contentAttributes = ref(null);
const { hasTranslations, translationContent } =
useTranslations(contentAttributes);
expect(hasTranslations.value).toBe(false);
expect(translationContent.value).toBeNull();
describe('selectTranslation', () => {
it('returns null when translations is null', () => {
expect(selectTranslation(null, 'en', 'en')).toBeNull();
});
it('returns false and null when translations are missing', () => {
const contentAttributes = ref({});
const { hasTranslations, translationContent } =
useTranslations(contentAttributes);
expect(hasTranslations.value).toBe(false);
expect(translationContent.value).toBeNull();
it('returns null when translations is empty', () => {
expect(selectTranslation({}, 'en', 'en')).toBeNull();
});
it('returns false and null when translations is an empty object', () => {
const contentAttributes = ref({ translations: {} });
const { hasTranslations, translationContent } =
useTranslations(contentAttributes);
expect(hasTranslations.value).toBe(false);
expect(translationContent.value).toBeNull();
it('returns first translation when no locale matches', () => {
const translations = { en: 'Hello', es: 'Hola' };
expect(selectTranslation(translations, 'fr', 'de')).toBe('Hello');
});
it('returns true and correct translation content when translations exist', () => {
const contentAttributes = ref({
translations: { en: 'Hello' },
});
const { hasTranslations, translationContent } =
useTranslations(contentAttributes);
expect(hasTranslations.value).toBe(true);
// Should return the first translation (en: 'Hello')
expect(translationContent.value).toBe('Hello');
it('returns translation matching agent locale', () => {
const translations = { en: 'Hello', es: 'Hola', zh_CN: '你好' };
expect(selectTranslation(translations, 'es', 'en')).toBe('Hola');
});
it('falls back to account locale when agent locale not found', () => {
const translations = { en: 'Hello', zh_CN: '你好' };
expect(selectTranslation(translations, 'fr', 'zh_CN')).toBe('你好');
});
it('returns first translation when both locales are undefined', () => {
const translations = { en: 'Hello', es: 'Hola' };
expect(selectTranslation(translations, undefined, undefined)).toBe('Hello');
});
});
@@ -1,4 +1,25 @@
import { computed } from 'vue';
import { useUISettings } from './useUISettings';
import { useAccount } from './useAccount';
/**
* Select translation based on locale priority.
* @param {Object} translations - Translations object with locale keys
* @param {string} agentLocale - Agent's preferred locale
* @param {string} accountLocale - Account's default locale
* @returns {string|null} Selected translation or null
*/
export function selectTranslation(translations, agentLocale, accountLocale) {
if (!translations || Object.keys(translations).length === 0) return null;
if (agentLocale && translations[agentLocale]) {
return translations[agentLocale];
}
if (accountLocale && translations[accountLocale]) {
return translations[accountLocale];
}
return translations[Object.keys(translations)[0]];
}
/**
* Composable to extract translation state/content from contentAttributes.
@@ -6,6 +27,9 @@ import { computed } from 'vue';
* @returns {Object} { hasTranslations, translationContent }
*/
export function useTranslations(contentAttributes) {
const { uiSettings } = useUISettings();
const { currentAccount } = useAccount();
const hasTranslations = computed(() => {
if (!contentAttributes.value) return false;
const { translations = {} } = contentAttributes.value;
@@ -14,8 +38,11 @@ export function useTranslations(contentAttributes) {
const translationContent = computed(() => {
if (!hasTranslations.value) return null;
const translations = contentAttributes.value.translations;
return translations[Object.keys(translations)[0]];
return selectTranslation(
contentAttributes.value.translations,
uiSettings.value?.locale,
currentAccount.value?.locale
);
});
return { hasTranslations, translationContent };
+11 -2
View File
@@ -140,6 +140,11 @@ export const FORMATTING = {
nodes: [],
menu: ['strong', 'em', 'link', 'undo', 'redo'],
},
'Context::Plain': {
marks: [],
nodes: [],
menu: [],
},
};
// Editor menu options for Full Editor
@@ -232,8 +237,12 @@ export const MARKDOWN_PATTERNS = [
patterns: [{ pattern: /`([^`]+)`/g, replacement: '$1' }],
},
{
type: 'link', // PM: link, eg: [text](url)
patterns: [{ pattern: /\[([^\]]+)\]\([^)]+\)/g, replacement: '$1' }],
type: 'link', // PM: link
patterns: [
{ pattern: /\[([^\]]+)\]\([^)]+\)/g, replacement: '$1' }, // [text](url) -> text
{ pattern: /<([a-zA-Z][a-zA-Z0-9+.-]*:[^\s>]+)>/g, replacement: '$1' }, // <https://...>, <mailto:...>, <tel:...>, <ftp://...>, etc
{ pattern: /<([^\s@]+@[^\s@>]+)>/g, replacement: '$1' }, // <user@example.com> -> user@example.com
],
},
];
@@ -5,4 +5,5 @@ export const LOCAL_STORAGE_KEYS = {
COLOR_SCHEME: 'color_scheme',
DISMISSED_LABEL_SUGGESTIONS: 'labelSuggestionsDismissed',
MESSAGE_REPLY_TO: 'messageReplyTo',
RECENT_SEARCHES: 'recentSearches',
};
+1
View File
@@ -43,6 +43,7 @@ export const FEATURE_FLAGS = {
SAML: 'saml',
QUOTED_EMAIL_REPLY: 'quoted_email_reply',
COMPANIES: 'companies',
ADVANCED_SEARCH: 'advanced_search',
};
export const PREMIUM_FEATURES = [
@@ -1,4 +1,4 @@
import posthog from 'posthog-js';
import * as amplitude from '@amplitude/analytics-browser';
/**
* AnalyticsHelper class to initialize and track user analytics
@@ -26,12 +26,10 @@ export class AnalyticsHelper {
return;
}
posthog.init(this.analyticsToken, {
api_host: 'https://app.posthog.com',
capture_pageview: false,
persistence: 'localStorage+cookie',
amplitude.init(this.analyticsToken, {
defaultTracking: false,
});
this.analytics = posthog;
this.analytics = amplitude;
}
/**
@@ -45,20 +43,26 @@ export class AnalyticsHelper {
}
this.user = user;
this.analytics.identify(this.user.id.toString(), {
email: this.user.email,
name: this.user.name,
avatar: this.user.avatar_url,
});
this.analytics.setUserId(`user-${this.user.id.toString()}`);
const identifyEvent = new amplitude.Identify();
identifyEvent.set('email', this.user.email);
identifyEvent.set('name', this.user.name);
identifyEvent.set('avatar', this.user.avatar_url);
this.analytics.identify(identifyEvent);
const { accounts, account_id: accountId } = this.user;
const [currentAccount] = accounts.filter(
account => account.id === accountId
);
if (currentAccount) {
this.analytics.group('company', currentAccount.id.toString(), {
name: currentAccount.name,
});
const groupId = `account-${currentAccount.id.toString()}`;
this.analytics.setGroup('company', groupId);
const groupIdentify = new amplitude.Identify();
groupIdentify.set('name', currentAccount.name);
this.analytics.groupIdentify('company', groupId, groupIdentify);
}
}
@@ -72,20 +76,21 @@ export class AnalyticsHelper {
if (!this.analytics) {
return;
}
this.analytics.capture(eventName, properties);
this.analytics.track(eventName, properties);
}
/**
* Track the page views
* @function
* @param {Object} params - Page view properties
* @param {string} pageName - Page name
* @param {Object} [properties={}] - Page view properties
*/
page(params) {
page(pageName, properties = {}) {
if (!this.analytics) {
return;
}
this.analytics.capture('$pageview', params);
this.analytics.track('$pageview', { pageName, ...properties });
}
}
@@ -1,12 +1,15 @@
import helperObject, { AnalyticsHelper } from '../';
vi.mock('posthog-js', () => ({
default: {
init: vi.fn(),
identify: vi.fn(),
capture: vi.fn(),
group: vi.fn(),
},
vi.mock('@amplitude/analytics-browser', () => ({
init: vi.fn(),
setUserId: vi.fn(),
identify: vi.fn(),
setGroup: vi.fn(),
groupIdentify: vi.fn(),
track: vi.fn(),
Identify: vi.fn(() => ({
set: vi.fn(),
})),
}));
describe('helperObject', () => {
@@ -22,12 +25,12 @@ describe('AnalyticsHelper', () => {
});
describe('init', () => {
it('should initialize posthog with the correct token', async () => {
it('should initialize amplitude with the correct token', async () => {
await analyticsHelper.init();
expect(analyticsHelper.analytics).not.toBe(null);
});
it('should not initialize posthog if token is not provided', async () => {
it('should not initialize amplitude if token is not provided', async () => {
analyticsHelper = new AnalyticsHelper();
await analyticsHelper.init();
expect(analyticsHelper.analytics).toBe(null);
@@ -36,10 +39,15 @@ describe('AnalyticsHelper', () => {
describe('identify', () => {
beforeEach(() => {
analyticsHelper.analytics = { identify: vi.fn(), group: vi.fn() };
analyticsHelper.analytics = {
setUserId: vi.fn(),
identify: vi.fn(),
setGroup: vi.fn(),
groupIdentify: vi.fn(),
};
});
it('should call identify on posthog with correct arguments', () => {
it('should call setUserId and identify on amplitude with correct arguments', () => {
analyticsHelper.identify({
id: 123,
email: 'test@example.com',
@@ -49,19 +57,18 @@ describe('AnalyticsHelper', () => {
account_id: 1,
});
expect(analyticsHelper.analytics.identify).toHaveBeenCalledWith('123', {
email: 'test@example.com',
name: 'Test User',
avatar: 'avatar_url',
});
expect(analyticsHelper.analytics.group).toHaveBeenCalledWith(
'company',
'1',
{ name: 'Account 1' }
expect(analyticsHelper.analytics.setUserId).toHaveBeenCalledWith(
'user-123'
);
expect(analyticsHelper.analytics.identify).toHaveBeenCalled();
expect(analyticsHelper.analytics.setGroup).toHaveBeenCalledWith(
'company',
'account-1'
);
expect(analyticsHelper.analytics.groupIdentify).toHaveBeenCalled();
});
it('should call identify on posthog without group', () => {
it('should call identify on amplitude without group', () => {
analyticsHelper.identify({
id: 123,
email: 'test@example.com',
@@ -71,10 +78,10 @@ describe('AnalyticsHelper', () => {
account_id: 5,
});
expect(analyticsHelper.analytics.group).not.toHaveBeenCalled();
expect(analyticsHelper.analytics.setGroup).not.toHaveBeenCalled();
});
it('should not call analytics.page if analytics is null', () => {
it('should not call analytics methods if analytics is null', () => {
analyticsHelper.analytics = null;
analyticsHelper.identify({});
expect(analyticsHelper.analytics).toBe(null);
@@ -83,27 +90,27 @@ describe('AnalyticsHelper', () => {
describe('track', () => {
beforeEach(() => {
analyticsHelper.analytics = { capture: vi.fn() };
analyticsHelper.analytics = { track: vi.fn() };
analyticsHelper.user = { id: 123 };
});
it('should call capture on posthog with correct arguments', () => {
it('should call track on amplitude with correct arguments', () => {
analyticsHelper.track('Test Event', { prop1: 'value1', prop2: 'value2' });
expect(analyticsHelper.analytics.capture).toHaveBeenCalledWith(
expect(analyticsHelper.analytics.track).toHaveBeenCalledWith(
'Test Event',
{ prop1: 'value1', prop2: 'value2' }
);
});
it('should call capture on posthog with default properties', () => {
it('should call track on amplitude with default properties', () => {
analyticsHelper.track('Test Event');
expect(analyticsHelper.analytics.capture).toHaveBeenCalledWith(
expect(analyticsHelper.analytics.track).toHaveBeenCalledWith(
'Test Event',
{}
);
});
it('should not call capture on posthog if analytics is not initialized', () => {
it('should not call track on amplitude if analytics is not initialized', () => {
analyticsHelper.analytics = null;
analyticsHelper.track('Test Event', { prop1: 'value1', prop2: 'value2' });
expect(analyticsHelper.analytics).toBe(null);
@@ -112,24 +119,25 @@ describe('AnalyticsHelper', () => {
describe('page', () => {
beforeEach(() => {
analyticsHelper.analytics = { capture: vi.fn() };
analyticsHelper.analytics = { track: vi.fn() };
});
it('should call the capture method for pageview with the correct arguments', () => {
const params = {
name: 'Test page',
url: '/test',
it('should call the track method for pageview with the correct arguments', () => {
const pageName = 'home';
const properties = {
path: '/test',
name: 'home',
};
analyticsHelper.page(params);
expect(analyticsHelper.analytics.capture).toHaveBeenCalledWith(
analyticsHelper.page(pageName, properties);
expect(analyticsHelper.analytics.track).toHaveBeenCalledWith(
'$pageview',
params
{ pageName: 'home', path: '/test', name: 'home' }
);
});
it('should not call analytics.capture if analytics is null', () => {
it('should not call analytics.track if analytics is null', () => {
analyticsHelper.analytics = null;
analyticsHelper.page();
analyticsHelper.page('home');
expect(analyticsHelper.analytics).toBe(null);
});
});
+17 -45
View File
@@ -34,6 +34,7 @@ export function extractTextFromMarkdown(markdown) {
/**
* Strip unsupported markdown formatting based on channel capabilities.
* Uses MARKDOWN_PATTERNS from editor constants.
*
* @param {string} markdown - markdown text to process
* @param {string} channelType - The channel type to check supported formatting
@@ -43,35 +44,16 @@ export function stripUnsupportedSignatureMarkdown(markdown, channelType) {
if (!markdown) return '';
const { marks = [], nodes = [] } = FORMATTING[channelType] || {};
const has = (arr, key) => arr.includes(key);
const supported = [...marks, ...nodes];
// Define stripping rules: [condition, pattern, replacement]
const rules = [
[!has(nodes, 'image'), /!\[.*?\]\(.*?\)/g, ''],
[!has(marks, 'link'), /\[([^\]]+)\]\([^)]+\)/g, '$1'],
[!has(nodes, 'codeBlock'), /```[\s\S]*?```/g, ''],
[!has(marks, 'code'), /`([^`]+)`/g, '$1'],
[!has(marks, 'strong'), /\*\*([^*]+)\*\*/g, '$1'],
[!has(marks, 'strong'), /__([^_]+)__/g, '$1'],
[!has(marks, 'em'), /\*([^*]+)\*/g, '$1'],
// Match _text_ only at word boundaries (whitespace/string start/end)
// Preserves underscores in URLs (e.g., https://example.com/path_name) and variable names
[
!has(marks, 'em'),
/(?<=^|[\s])_([^_\s][^_]*[^_\s]|[^_\s])_(?=$|[\s])/g,
'$1',
],
[!has(marks, 'strike'), /~~([^~]+)~~/g, '$1'],
[!has(nodes, 'blockquote'), /^>\s?/gm, ''],
[!has(nodes, 'bulletList'), /^[-*+]\s+/gm, ''],
[!has(nodes, 'orderedList'), /^\d+\.\s+/gm, ''],
];
const result = rules.reduce(
(text, [shouldStrip, pattern, replacement]) =>
shouldStrip ? text.replace(pattern, replacement) : text,
markdown
);
// Apply patterns from MARKDOWN_PATTERNS for unsupported types
const result = MARKDOWN_PATTERNS.reduce((text, { type, patterns }) => {
if (supported.includes(type)) return text;
return patterns.reduce(
(t, { pattern, replacement }) => t.replace(pattern, replacement),
text
);
}, markdown);
return result
.split('\n')
@@ -186,27 +168,22 @@ export function appendSignature(body, signature, channelType) {
/**
* Removes the signature from the body, along with the signature delimiter.
* Tries to find both the original signature and the stripped version.
* Tries multiple signature variants: original, channel-stripped, and fully stripped.
*
* @param {string} body - The body to remove the signature from.
* @param {string} signature - The signature to remove.
* @param {string} channelType - Optional. The effective channel type for channel-specific stripping.
* For Twilio channels, pass the result of getEffectiveChannelType().
* @returns {string} - The body with the signature removed.
*/
export function removeSignature(body, signature, channelType) {
// Build list of signatures to try: original, channel-stripped, and fully stripped
const cleanedSignature = cleanSignature(signature);
// Build unique list of signature variants to try
const channelStripped = channelType
? cleanSignature(stripUnsupportedSignatureMarkdown(signature, channelType))
: null;
const fullyStripped = cleanSignature(extractTextFromMarkdown(signature));
// Try signatures in order: original → channel-specific → fully stripped
const signaturesToTry = [
cleanedSignature,
cleanSignature(signature),
channelStripped,
fullyStripped,
cleanSignature(extractTextFromMarkdown(signature)),
].filter((sig, i, arr) => sig && arr.indexOf(sig) === i); // Remove nulls and duplicates
// Find the first matching signature
@@ -225,17 +202,12 @@ export function removeSignature(body, signature, channelType) {
newBody = newBody.substring(0, signatureIndex).trimEnd();
}
// let's find the delimiter and remove it
const delimiterIndex = newBody.lastIndexOf(SIGNATURE_DELIMITER);
if (
delimiterIndex !== -1 &&
delimiterIndex === newBody.length - SIGNATURE_DELIMITER.length // this will ensure the delimiter is at the end
) {
// Remove delimiter if it's at the end
if (newBody.endsWith(SIGNATURE_DELIMITER)) {
// if the delimiter is at the end, remove it
newBody = newBody.substring(0, delimiterIndex);
newBody = newBody.slice(0, -SIGNATURE_DELIMITER.length);
}
// return the value
return newBody;
}
@@ -896,6 +896,22 @@ describe('stripUnsupportedFormatting', () => {
expect(stripUnsupportedFormatting(content, fullSchema)).toBe(content);
});
it('preserves autolinks when schema supports links', () => {
const content = 'Check out <https://cegrafic.com/catalogo/>';
expect(stripUnsupportedFormatting(content, fullSchema)).toBe(content);
});
it('preserves various URI scheme autolinks', () => {
const content =
'Email <mailto:user@example.com> or call <tel:+1234567890>';
expect(stripUnsupportedFormatting(content, fullSchema)).toBe(content);
});
it('preserves email autolinks', () => {
const content = 'Contact us at <support@chatwoot.com>';
expect(stripUnsupportedFormatting(content, fullSchema)).toBe(content);
});
it('preserves lists when schema supports them', () => {
const content = '- item 1\n- item 2\n1. first\n2. second';
expect(stripUnsupportedFormatting(content, fullSchema)).toBe(content);
@@ -967,6 +983,38 @@ describe('stripUnsupportedFormatting', () => {
).toBe('Check this link');
});
it('converts autolinks to plain URLs when schema does not support links', () => {
const content = 'Visit <https://cegrafic.com/catalogo/> for more info';
const expected = 'Visit https://cegrafic.com/catalogo/ for more info';
expect(stripUnsupportedFormatting(content, emptySchema)).toBe(expected);
});
it('handles multiple autolinks in content', () => {
const content = 'Check <https://example.com> and <https://test.com>';
const expected = 'Check https://example.com and https://test.com';
expect(stripUnsupportedFormatting(content, emptySchema)).toBe(expected);
});
it('converts URI scheme autolinks to plain text', () => {
const content =
'Email <mailto:support@example.com> or call <tel:+1234567890>';
const expected =
'Email mailto:support@example.com or call tel:+1234567890';
expect(stripUnsupportedFormatting(content, emptySchema)).toBe(expected);
});
it('converts email autolinks to plain text', () => {
const content = 'Reach us at <admin@chatwoot.com> for help';
const expected = 'Reach us at admin@chatwoot.com for help';
expect(stripUnsupportedFormatting(content, emptySchema)).toBe(expected);
});
it('handles mixed autolink types', () => {
const content = 'Visit <https://example.com> or email <info@example.com>';
const expected = 'Visit https://example.com or email info@example.com';
expect(stripUnsupportedFormatting(content, emptySchema)).toBe(expected);
});
it('strips bullet list markers', () => {
expect(
stripUnsupportedFormatting('- item 1\n- item 2', emptySchema)
@@ -6,7 +6,8 @@
"OPTIONS": {
"NAME": "Name",
"DOMAIN": "Domain",
"CREATED_AT": "Created at"
"CREATED_AT": "Created at",
"CONTACTS_COUNT": "Contacts count"
}
},
"ORDER": {
@@ -28,6 +28,7 @@
"TYPES": {
"MEDIA": "Media",
"QUICK_REPLY": "Quick Reply",
"CALL_TO_ACTION": "Call to Action",
"TEXT": "Text"
}
},
@@ -275,6 +275,16 @@
"SIDEBAR": {
"CONTACT": "Contact",
"COPILOT": "Copilot"
},
"VOICE_WIDGET": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NOT_ANSWERED_YET": "Not answered yet",
"HANDLED_IN_ANOTHER_TAB": "Being handled in another tab",
"REJECT_CALL": "Reject",
"JOIN_CALL": "Join call",
"END_CALL": "End call"
}
},
"EMAIL_TRANSCRIPT": {
@@ -808,6 +808,35 @@
"LABEL": "Message",
"PLACEHOLDER": "Please enter a message to show users with the form"
},
"BUTTON_TEXT": {
"LABEL": "Button text",
"PLACEHOLDER": "Please rate us"
},
"LANGUAGE": {
"LABEL": "Language",
"PLACEHOLDER": "Select template language"
},
"MESSAGE_PREVIEW": {
"LABEL": "Message preview",
"TOOLTIP": "This may vary slightly when rendered on WhatsApp's platform."
},
"TEMPLATE_STATUS": {
"APPROVED": "Approved by WhatsApp",
"PENDING": "Pending WhatsApp approval",
"REJECTED": "Meta rejected the template",
"DEFAULT": "Needs WhatsApp approval",
"NOT_FOUND": "The template does not exist in the Meta platform."
},
"TEMPLATE_CREATION": {
"SUCCESS_MESSAGE": "WhatsApp template created successfully and sent for approval",
"ERROR_MESSAGE": "Failed to create WhatsApp template"
},
"TEMPLATE_UPDATE_DIALOG": {
"TITLE": "Edit survey details",
"DESCRIPTION": "We will delete the previous template and make a new one which will be sent again for WhatsApp approval",
"CONFIRM": "Create new template",
"CANCEL": "Go back"
},
"SURVEY_RULE": {
"LABEL": "Survey rule",
"DESCRIPTION_PREFIX": "Send the survey if the conversation",
@@ -819,6 +848,7 @@
"SELECT_PLACEHOLDER": "select labels"
},
"NOTE": "Note: CSAT surveys are sent only once per conversation",
"WHATSAPP_NOTE": "Note: We will create a template and send it for WhatsApp approval. After being approved, surveys will be sent only once per conversation as per the survey rule.",
"API": {
"SUCCESS_MESSAGE": "CSAT settings updated successfully",
"ERROR_MESSAGE": "We couldn't update CSAT settings. Please try again later."
@@ -1,7 +1,7 @@
{
"SEARCH": {
"TABS": {
"ALL": "All",
"ALL": "All results",
"CONTACTS": "Contacts",
"CONVERSATIONS": "Conversations",
"MESSAGES": "Messages",
@@ -19,14 +19,50 @@
"LOADING_DATA": "Loading",
"EMPTY_STATE": "No {item} found for query '{query}'",
"EMPTY_STATE_FULL": "No results found for query '{query}'",
"PLACEHOLDER_KEYBINDING": "/ to focus",
"PLACEHOLDER_KEYBINDING": "/to focus",
"INPUT_PLACEHOLDER": "Type 3 or more characters to search",
"RECENT_SEARCHES": "Recent searches",
"CLEAR_ALL": "Clear all",
"MOST_RECENT": "Most recent",
"EMPTY_STATE_DEFAULT": "Search by conversation id, email, phone number, messages for better search results. ",
"BOT_LABEL": "Bot",
"READ_MORE": "Read more",
"READ_LESS": "Read less",
"WROTE": "wrote:",
"FROM": "from",
"EMAIL": "email",
"EMAIL_SUBJECT": "subject"
"FROM": "From",
"EMAIL": "Email",
"EMAIL_SUBJECT": "Subject",
"PRIVATE": "Private note",
"TRANSCRIPT": "Transcript",
"CREATED_AT": "created {time}",
"UPDATED_AT": "updated {time}",
"SORT_BY": {
"RELEVANCE": "Relevance"
},
"DATE_RANGE": {
"LAST_7_DAYS": "Last 7 days",
"LAST_30_DAYS": "Last 30 days",
"LAST_60_DAYS": "Last 60 days",
"LAST_90_DAYS": "Last 90 days",
"CUSTOM_RANGE": "Custom range:",
"CREATED_BETWEEN": "Created between",
"AND": "and",
"APPLY": "Apply",
"BEFORE_DATE": "Before {date}",
"AFTER_DATE": "After {date}",
"TIME_RANGE": "Filter by time",
"CLEAR_FILTER": "Clear filter"
},
"FILTERS": {
"FILTER_MESSAGE": "Filter messages by:",
"FROM": "Sender",
"IN": "Inbox",
"AGENTS": "Agents",
"CONTACTS": "Contacts",
"INBOXES": "Inboxes",
"NO_AGENTS": "No agents found",
"NO_CONTACTS": "Start by searching to see results",
"NO_INBOXES": "No inboxes found"
}
}
}
@@ -6,7 +6,8 @@
"OPTIONS": {
"NAME": "الاسم",
"DOMAIN": "النطاق",
"CREATED_AT": "تم إنشاؤها في"
"CREATED_AT": "تم إنشاؤها في",
"CONTACTS_COUNT": "Contacts count"
}
},
"ORDER": {
@@ -28,6 +28,7 @@
"TYPES": {
"MEDIA": "Media",
"QUICK_REPLY": "Quick Reply",
"CALL_TO_ACTION": "Call to Action",
"TEXT": "النص"
}
},
@@ -275,6 +275,16 @@
"SIDEBAR": {
"CONTACT": "جهات الاتصال",
"COPILOT": "Copilot"
},
"VOICE_WIDGET": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NOT_ANSWERED_YET": "Not answered yet",
"HANDLED_IN_ANOTHER_TAB": "Being handled in another tab",
"REJECT_CALL": "Reject",
"JOIN_CALL": "Join call",
"END_CALL": "End call"
}
},
"EMAIL_TRANSCRIPT": {
@@ -808,6 +808,35 @@
"LABEL": "رسالة",
"PLACEHOLDER": "Please enter a message to show users with the form"
},
"BUTTON_TEXT": {
"LABEL": "Button text",
"PLACEHOLDER": "Please rate us"
},
"LANGUAGE": {
"LABEL": "اللغة",
"PLACEHOLDER": "Select template language"
},
"MESSAGE_PREVIEW": {
"LABEL": "Message preview",
"TOOLTIP": "This may vary slightly when rendered on WhatsApp's platform."
},
"TEMPLATE_STATUS": {
"APPROVED": "Approved by WhatsApp",
"PENDING": "Pending WhatsApp approval",
"REJECTED": "Meta rejected the template",
"DEFAULT": "Needs WhatsApp approval",
"NOT_FOUND": "The template does not exist in the Meta platform."
},
"TEMPLATE_CREATION": {
"SUCCESS_MESSAGE": "WhatsApp template created successfully and sent for approval",
"ERROR_MESSAGE": "Failed to create WhatsApp template"
},
"TEMPLATE_UPDATE_DIALOG": {
"TITLE": "Edit survey details",
"DESCRIPTION": "We will delete the previous template and make a new one which will be sent again for WhatsApp approval",
"CONFIRM": "Create new template",
"CANCEL": "العودة للخلف"
},
"SURVEY_RULE": {
"LABEL": "Survey rule",
"DESCRIPTION_PREFIX": "Send the survey if the conversation",
@@ -819,6 +848,7 @@
"SELECT_PLACEHOLDER": "select labels"
},
"NOTE": "Note: CSAT surveys are sent only once per conversation",
"WHATSAPP_NOTE": "Note: We will create a template and send it for WhatsApp approval. After being approved, surveys will be sent only once per conversation as per the survey rule.",
"API": {
"SUCCESS_MESSAGE": "CSAT settings updated successfully",
"ERROR_MESSAGE": "We couldn't update CSAT settings. Please try again later."
@@ -1,7 +1,7 @@
{
"SEARCH": {
"TABS": {
"ALL": "الكل",
"ALL": "All results",
"CONTACTS": "جهات الاتصال",
"CONVERSATIONS": "المحادثات",
"MESSAGES": "الرسائل",
@@ -19,14 +19,50 @@
"LOADING_DATA": "جار التحميل",
"EMPTY_STATE": "لم يتم العثور على {item} للطلب '{query}'",
"EMPTY_STATE_FULL": "لم يتم العثور على نتائج للطلب '{query}'",
"PLACEHOLDER_KEYBINDING": "/ للتركيز",
"PLACEHOLDER_KEYBINDING": "/للتركيز",
"INPUT_PLACEHOLDER": "أكتب 3 أحرف أو أكثر للبحث",
"RECENT_SEARCHES": "Recent searches",
"CLEAR_ALL": "Clear all",
"MOST_RECENT": "Most recent",
"EMPTY_STATE_DEFAULT": "البحث عن طريق معرف المحادثة أو البريد الإلكتروني أو رقم الهاتف أو الرسائل للحصول على نتائج بحث أفضل. ",
"BOT_LABEL": "رد آلي",
"READ_MORE": "اقرأ المزيد",
"READ_LESS": "Read less",
"WROTE": "كتب:",
"FROM": "من",
"EMAIL": "البريد الإلكتروني",
"EMAIL_SUBJECT": "الموضوع"
"EMAIL_SUBJECT": "الموضوع",
"PRIVATE": "Private note",
"TRANSCRIPT": "Transcript",
"CREATED_AT": "created {time}",
"UPDATED_AT": "updated {time}",
"SORT_BY": {
"RELEVANCE": "Relevance"
},
"DATE_RANGE": {
"LAST_7_DAYS": "آخر 7 أيام",
"LAST_30_DAYS": "آخر 30 يوماً",
"LAST_60_DAYS": "آخر 60 يوماً",
"LAST_90_DAYS": "آخر 90 يوماً",
"CUSTOM_RANGE": "Custom range:",
"CREATED_BETWEEN": "Created between",
"AND": "و",
"APPLY": "تطبيق",
"BEFORE_DATE": "Before {date}",
"AFTER_DATE": "After {date}",
"TIME_RANGE": "Filter by time",
"CLEAR_FILTER": "Clear filter"
},
"FILTERS": {
"FILTER_MESSAGE": "Filter messages by:",
"FROM": "المرسل",
"IN": "صندوق الوارد",
"AGENTS": "الوكلاء",
"CONTACTS": "جهات الاتصال",
"INBOXES": "قنوات التواصل",
"NO_AGENTS": "لم يتم العثور على وكلاء",
"NO_CONTACTS": "Start by searching to see results",
"NO_INBOXES": "No inboxes found"
}
}
}
@@ -6,7 +6,8 @@
"OPTIONS": {
"NAME": "Name",
"DOMAIN": "Domain",
"CREATED_AT": "Created at"
"CREATED_AT": "Created at",
"CONTACTS_COUNT": "Contacts count"
}
},
"ORDER": {
@@ -28,6 +28,7 @@
"TYPES": {
"MEDIA": "Media",
"QUICK_REPLY": "Quick Reply",
"CALL_TO_ACTION": "Call to Action",
"TEXT": "Text"
}
},
@@ -275,6 +275,16 @@
"SIDEBAR": {
"CONTACT": "Contact",
"COPILOT": "Copilot"
},
"VOICE_WIDGET": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NOT_ANSWERED_YET": "Not answered yet",
"HANDLED_IN_ANOTHER_TAB": "Being handled in another tab",
"REJECT_CALL": "Reject",
"JOIN_CALL": "Join call",
"END_CALL": "End call"
}
},
"EMAIL_TRANSCRIPT": {
@@ -808,6 +808,35 @@
"LABEL": "Message",
"PLACEHOLDER": "Please enter a message to show users with the form"
},
"BUTTON_TEXT": {
"LABEL": "Button text",
"PLACEHOLDER": "Please rate us"
},
"LANGUAGE": {
"LABEL": "Language",
"PLACEHOLDER": "Select template language"
},
"MESSAGE_PREVIEW": {
"LABEL": "Message preview",
"TOOLTIP": "This may vary slightly when rendered on WhatsApp's platform."
},
"TEMPLATE_STATUS": {
"APPROVED": "Approved by WhatsApp",
"PENDING": "Pending WhatsApp approval",
"REJECTED": "Meta rejected the template",
"DEFAULT": "Needs WhatsApp approval",
"NOT_FOUND": "The template does not exist in the Meta platform."
},
"TEMPLATE_CREATION": {
"SUCCESS_MESSAGE": "WhatsApp template created successfully and sent for approval",
"ERROR_MESSAGE": "Failed to create WhatsApp template"
},
"TEMPLATE_UPDATE_DIALOG": {
"TITLE": "Edit survey details",
"DESCRIPTION": "We will delete the previous template and make a new one which will be sent again for WhatsApp approval",
"CONFIRM": "Create new template",
"CANCEL": "Go back"
},
"SURVEY_RULE": {
"LABEL": "Survey rule",
"DESCRIPTION_PREFIX": "Send the survey if the conversation",
@@ -819,6 +848,7 @@
"SELECT_PLACEHOLDER": "select labels"
},
"NOTE": "Note: CSAT surveys are sent only once per conversation",
"WHATSAPP_NOTE": "Note: We will create a template and send it for WhatsApp approval. After being approved, surveys will be sent only once per conversation as per the survey rule.",
"API": {
"SUCCESS_MESSAGE": "CSAT settings updated successfully",
"ERROR_MESSAGE": "We couldn't update CSAT settings. Please try again later."
@@ -1,7 +1,7 @@
{
"SEARCH": {
"TABS": {
"ALL": "All",
"ALL": "All results",
"CONTACTS": "Contacts",
"CONVERSATIONS": "Conversations",
"MESSAGES": "Messages",
@@ -19,14 +19,50 @@
"LOADING_DATA": "Loading",
"EMPTY_STATE": "No {item} found for query '{query}'",
"EMPTY_STATE_FULL": "No results found for query '{query}'",
"PLACEHOLDER_KEYBINDING": "/ to focus",
"PLACEHOLDER_KEYBINDING": "/to focus",
"INPUT_PLACEHOLDER": "Type 3 or more characters to search",
"RECENT_SEARCHES": "Recent searches",
"CLEAR_ALL": "Clear all",
"MOST_RECENT": "Most recent",
"EMPTY_STATE_DEFAULT": "Search by conversation id, email, phone number, messages for better search results. ",
"BOT_LABEL": "Bot",
"READ_MORE": "Read more",
"READ_LESS": "Read less",
"WROTE": "wrote:",
"FROM": "from",
"EMAIL": "email",
"EMAIL_SUBJECT": "subject"
"FROM": "From",
"EMAIL": "Email",
"EMAIL_SUBJECT": "Subject",
"PRIVATE": "Private note",
"TRANSCRIPT": "Transcript",
"CREATED_AT": "created {time}",
"UPDATED_AT": "updated {time}",
"SORT_BY": {
"RELEVANCE": "Relevance"
},
"DATE_RANGE": {
"LAST_7_DAYS": "Last 7 days",
"LAST_30_DAYS": "Last 30 days",
"LAST_60_DAYS": "Last 60 days",
"LAST_90_DAYS": "Last 90 days",
"CUSTOM_RANGE": "Custom range:",
"CREATED_BETWEEN": "Created between",
"AND": "and",
"APPLY": "Apply",
"BEFORE_DATE": "Before {date}",
"AFTER_DATE": "After {date}",
"TIME_RANGE": "Filter by time",
"CLEAR_FILTER": "Clear filter"
},
"FILTERS": {
"FILTER_MESSAGE": "Filter messages by:",
"FROM": "Sender",
"IN": "Inbox",
"AGENTS": "Agents",
"CONTACTS": "Contacts",
"INBOXES": "Inboxes",
"NO_AGENTS": "No agents found",
"NO_CONTACTS": "Start by searching to see results",
"NO_INBOXES": "No inboxes found"
}
}
}
@@ -6,7 +6,8 @@
"OPTIONS": {
"NAME": "Име",
"DOMAIN": "Domain",
"CREATED_AT": "Създаден в"
"CREATED_AT": "Създаден в",
"CONTACTS_COUNT": "Contacts count"
}
},
"ORDER": {
@@ -28,6 +28,7 @@
"TYPES": {
"MEDIA": "Media",
"QUICK_REPLY": "Quick Reply",
"CALL_TO_ACTION": "Call to Action",
"TEXT": "Text"
}
},
@@ -275,6 +275,16 @@
"SIDEBAR": {
"CONTACT": "Контакт",
"COPILOT": "Copilot"
},
"VOICE_WIDGET": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NOT_ANSWERED_YET": "Not answered yet",
"HANDLED_IN_ANOTHER_TAB": "Being handled in another tab",
"REJECT_CALL": "Reject",
"JOIN_CALL": "Join call",
"END_CALL": "End call"
}
},
"EMAIL_TRANSCRIPT": {
@@ -808,6 +808,35 @@
"LABEL": "Съобщение",
"PLACEHOLDER": "Please enter a message to show users with the form"
},
"BUTTON_TEXT": {
"LABEL": "Button text",
"PLACEHOLDER": "Please rate us"
},
"LANGUAGE": {
"LABEL": "Language",
"PLACEHOLDER": "Select template language"
},
"MESSAGE_PREVIEW": {
"LABEL": "Message preview",
"TOOLTIP": "This may vary slightly when rendered on WhatsApp's platform."
},
"TEMPLATE_STATUS": {
"APPROVED": "Approved by WhatsApp",
"PENDING": "Pending WhatsApp approval",
"REJECTED": "Meta rejected the template",
"DEFAULT": "Needs WhatsApp approval",
"NOT_FOUND": "The template does not exist in the Meta platform."
},
"TEMPLATE_CREATION": {
"SUCCESS_MESSAGE": "WhatsApp template created successfully and sent for approval",
"ERROR_MESSAGE": "Failed to create WhatsApp template"
},
"TEMPLATE_UPDATE_DIALOG": {
"TITLE": "Edit survey details",
"DESCRIPTION": "We will delete the previous template and make a new one which will be sent again for WhatsApp approval",
"CONFIRM": "Create new template",
"CANCEL": "Go back"
},
"SURVEY_RULE": {
"LABEL": "Survey rule",
"DESCRIPTION_PREFIX": "Send the survey if the conversation",
@@ -819,6 +848,7 @@
"SELECT_PLACEHOLDER": "select labels"
},
"NOTE": "Note: CSAT surveys are sent only once per conversation",
"WHATSAPP_NOTE": "Note: We will create a template and send it for WhatsApp approval. After being approved, surveys will be sent only once per conversation as per the survey rule.",
"API": {
"SUCCESS_MESSAGE": "CSAT settings updated successfully",
"ERROR_MESSAGE": "We couldn't update CSAT settings. Please try again later."
@@ -1,7 +1,7 @@
{
"SEARCH": {
"TABS": {
"ALL": "Всички",
"ALL": "All results",
"CONTACTS": "Контакти",
"CONVERSATIONS": "Разговори",
"MESSAGES": "Messages",
@@ -19,14 +19,50 @@
"LOADING_DATA": "Loading",
"EMPTY_STATE": "No {item} found for query '{query}'",
"EMPTY_STATE_FULL": "No results found for query '{query}'",
"PLACEHOLDER_KEYBINDING": "/ to focus",
"PLACEHOLDER_KEYBINDING": "/to focus",
"INPUT_PLACEHOLDER": "Search messages, contacts or conversations",
"RECENT_SEARCHES": "Recent searches",
"CLEAR_ALL": "Clear all",
"MOST_RECENT": "Most recent",
"EMPTY_STATE_DEFAULT": "Search by conversation id, email, phone number, messages for better search results.",
"BOT_LABEL": "Бот",
"READ_MORE": "Read more",
"READ_LESS": "Read less",
"WROTE": "wrote:",
"FROM": "от",
"EMAIL": "имейл",
"EMAIL_SUBJECT": "тема"
"FROM": "From",
"EMAIL": "Email",
"EMAIL_SUBJECT": "Subject",
"PRIVATE": "Private note",
"TRANSCRIPT": "Transcript",
"CREATED_AT": "created {time}",
"UPDATED_AT": "updated {time}",
"SORT_BY": {
"RELEVANCE": "Relevance"
},
"DATE_RANGE": {
"LAST_7_DAYS": "Last 7 days",
"LAST_30_DAYS": "Last 30 days",
"LAST_60_DAYS": "Last 60 days",
"LAST_90_DAYS": "Last 90 days",
"CUSTOM_RANGE": "Custom range:",
"CREATED_BETWEEN": "Created between",
"AND": "and",
"APPLY": "Apply",
"BEFORE_DATE": "Before {date}",
"AFTER_DATE": "After {date}",
"TIME_RANGE": "Filter by time",
"CLEAR_FILTER": "Clear filter"
},
"FILTERS": {
"FILTER_MESSAGE": "Filter messages by:",
"FROM": "Sender",
"IN": "Входяща кутия",
"AGENTS": "Агенти",
"CONTACTS": "Контакти",
"INBOXES": "Inboxes",
"NO_AGENTS": "Няма намерени агенти",
"NO_CONTACTS": "Start by searching to see results",
"NO_INBOXES": "No inboxes found"
}
}
}
@@ -6,7 +6,8 @@
"OPTIONS": {
"NAME": "Name",
"DOMAIN": "Domain",
"CREATED_AT": "Created at"
"CREATED_AT": "Created at",
"CONTACTS_COUNT": "Contacts count"
}
},
"ORDER": {
@@ -28,6 +28,7 @@
"TYPES": {
"MEDIA": "Media",
"QUICK_REPLY": "Quick Reply",
"CALL_TO_ACTION": "Call to Action",
"TEXT": "Text"
}
},
@@ -275,6 +275,16 @@
"SIDEBAR": {
"CONTACT": "Contact",
"COPILOT": "Copilot"
},
"VOICE_WIDGET": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NOT_ANSWERED_YET": "Not answered yet",
"HANDLED_IN_ANOTHER_TAB": "Being handled in another tab",
"REJECT_CALL": "Reject",
"JOIN_CALL": "Join call",
"END_CALL": "End call"
}
},
"EMAIL_TRANSCRIPT": {
@@ -808,6 +808,35 @@
"LABEL": "Message",
"PLACEHOLDER": "Please enter a message to show users with the form"
},
"BUTTON_TEXT": {
"LABEL": "Button text",
"PLACEHOLDER": "Please rate us"
},
"LANGUAGE": {
"LABEL": "Language",
"PLACEHOLDER": "Select template language"
},
"MESSAGE_PREVIEW": {
"LABEL": "Message preview",
"TOOLTIP": "This may vary slightly when rendered on WhatsApp's platform."
},
"TEMPLATE_STATUS": {
"APPROVED": "Approved by WhatsApp",
"PENDING": "Pending WhatsApp approval",
"REJECTED": "Meta rejected the template",
"DEFAULT": "Needs WhatsApp approval",
"NOT_FOUND": "The template does not exist in the Meta platform."
},
"TEMPLATE_CREATION": {
"SUCCESS_MESSAGE": "WhatsApp template created successfully and sent for approval",
"ERROR_MESSAGE": "Failed to create WhatsApp template"
},
"TEMPLATE_UPDATE_DIALOG": {
"TITLE": "Edit survey details",
"DESCRIPTION": "We will delete the previous template and make a new one which will be sent again for WhatsApp approval",
"CONFIRM": "Create new template",
"CANCEL": "Go back"
},
"SURVEY_RULE": {
"LABEL": "Survey rule",
"DESCRIPTION_PREFIX": "Send the survey if the conversation",
@@ -819,6 +848,7 @@
"SELECT_PLACEHOLDER": "select labels"
},
"NOTE": "Note: CSAT surveys are sent only once per conversation",
"WHATSAPP_NOTE": "Note: We will create a template and send it for WhatsApp approval. After being approved, surveys will be sent only once per conversation as per the survey rule.",
"API": {
"SUCCESS_MESSAGE": "CSAT settings updated successfully",
"ERROR_MESSAGE": "We couldn't update CSAT settings. Please try again later."
@@ -1,7 +1,7 @@
{
"SEARCH": {
"TABS": {
"ALL": "All",
"ALL": "All results",
"CONTACTS": "Contacts",
"CONVERSATIONS": "Conversations",
"MESSAGES": "Messages",
@@ -19,14 +19,50 @@
"LOADING_DATA": "Loading",
"EMPTY_STATE": "No {item} found for query '{query}'",
"EMPTY_STATE_FULL": "No results found for query '{query}'",
"PLACEHOLDER_KEYBINDING": "/ to focus",
"PLACEHOLDER_KEYBINDING": "/to focus",
"INPUT_PLACEHOLDER": "Type 3 or more characters to search",
"RECENT_SEARCHES": "Recent searches",
"CLEAR_ALL": "Clear all",
"MOST_RECENT": "Most recent",
"EMPTY_STATE_DEFAULT": "Search by conversation id, email, phone number, messages for better search results. ",
"BOT_LABEL": "Bot",
"READ_MORE": "Read more",
"READ_LESS": "Read less",
"WROTE": "wrote:",
"FROM": "from",
"EMAIL": "email",
"EMAIL_SUBJECT": "subject"
"FROM": "From",
"EMAIL": "Email",
"EMAIL_SUBJECT": "Subject",
"PRIVATE": "Private note",
"TRANSCRIPT": "Transcript",
"CREATED_AT": "created {time}",
"UPDATED_AT": "updated {time}",
"SORT_BY": {
"RELEVANCE": "Relevance"
},
"DATE_RANGE": {
"LAST_7_DAYS": "Last 7 days",
"LAST_30_DAYS": "Last 30 days",
"LAST_60_DAYS": "Last 60 days",
"LAST_90_DAYS": "Last 90 days",
"CUSTOM_RANGE": "Custom range:",
"CREATED_BETWEEN": "Created between",
"AND": "and",
"APPLY": "Apply",
"BEFORE_DATE": "Before {date}",
"AFTER_DATE": "After {date}",
"TIME_RANGE": "Filter by time",
"CLEAR_FILTER": "Clear filter"
},
"FILTERS": {
"FILTER_MESSAGE": "Filter messages by:",
"FROM": "Sender",
"IN": "Inbox",
"AGENTS": "Agents",
"CONTACTS": "Contacts",
"INBOXES": "Inboxes",
"NO_AGENTS": "No agents found",
"NO_CONTACTS": "Start by searching to see results",
"NO_INBOXES": "No inboxes found"
}
}
}
@@ -6,7 +6,8 @@
"OPTIONS": {
"NAME": "Nom",
"DOMAIN": "Domini",
"CREATED_AT": "Creat per"
"CREATED_AT": "Creat per",
"CONTACTS_COUNT": "Contacts count"
}
},
"ORDER": {
@@ -28,6 +28,7 @@
"TYPES": {
"MEDIA": "Media",
"QUICK_REPLY": "Quick Reply",
"CALL_TO_ACTION": "Call to Action",
"TEXT": "Llista"
}
},
@@ -275,6 +275,16 @@
"SIDEBAR": {
"CONTACT": "Contacte",
"COPILOT": "Copilot"
},
"VOICE_WIDGET": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NOT_ANSWERED_YET": "Not answered yet",
"HANDLED_IN_ANOTHER_TAB": "Being handled in another tab",
"REJECT_CALL": "Reject",
"JOIN_CALL": "Join call",
"END_CALL": "End call"
}
},
"EMAIL_TRANSCRIPT": {
@@ -808,6 +808,35 @@
"LABEL": "Missatge",
"PLACEHOLDER": "Please enter a message to show users with the form"
},
"BUTTON_TEXT": {
"LABEL": "Button text",
"PLACEHOLDER": "Please rate us"
},
"LANGUAGE": {
"LABEL": "Idioma",
"PLACEHOLDER": "Select template language"
},
"MESSAGE_PREVIEW": {
"LABEL": "Message preview",
"TOOLTIP": "This may vary slightly when rendered on WhatsApp's platform."
},
"TEMPLATE_STATUS": {
"APPROVED": "Approved by WhatsApp",
"PENDING": "Pending WhatsApp approval",
"REJECTED": "Meta rejected the template",
"DEFAULT": "Needs WhatsApp approval",
"NOT_FOUND": "The template does not exist in the Meta platform."
},
"TEMPLATE_CREATION": {
"SUCCESS_MESSAGE": "WhatsApp template created successfully and sent for approval",
"ERROR_MESSAGE": "Failed to create WhatsApp template"
},
"TEMPLATE_UPDATE_DIALOG": {
"TITLE": "Edit survey details",
"DESCRIPTION": "We will delete the previous template and make a new one which will be sent again for WhatsApp approval",
"CONFIRM": "Create new template",
"CANCEL": "Torna"
},
"SURVEY_RULE": {
"LABEL": "Survey rule",
"DESCRIPTION_PREFIX": "Send the survey if the conversation",
@@ -819,6 +848,7 @@
"SELECT_PLACEHOLDER": "select labels"
},
"NOTE": "Note: CSAT surveys are sent only once per conversation",
"WHATSAPP_NOTE": "Note: We will create a template and send it for WhatsApp approval. After being approved, surveys will be sent only once per conversation as per the survey rule.",
"API": {
"SUCCESS_MESSAGE": "CSAT settings updated successfully",
"ERROR_MESSAGE": "We couldn't update CSAT settings. Please try again later."
@@ -1,7 +1,7 @@
{
"SEARCH": {
"TABS": {
"ALL": "Totes",
"ALL": "All results",
"CONTACTS": "Contactes",
"CONVERSATIONS": "Converses",
"MESSAGES": "Missatges",
@@ -19,14 +19,50 @@
"LOADING_DATA": "Carregant",
"EMPTY_STATE": "No s'ha trobat cap {item} per a la consulta '{query}'",
"EMPTY_STATE_FULL": "No s'han trobat resultats per a la consulta '{query}'",
"PLACEHOLDER_KEYBINDING": "/ centrar",
"PLACEHOLDER_KEYBINDING": "/centrar",
"INPUT_PLACEHOLDER": "Search messages, contacts or conversations",
"RECENT_SEARCHES": "Recent searches",
"CLEAR_ALL": "Esborrar tot",
"MOST_RECENT": "Most recent",
"EMPTY_STATE_DEFAULT": "Search by conversation id, email, phone number, messages for better search results.",
"BOT_LABEL": "Bot",
"READ_MORE": "Llegir més",
"READ_LESS": "Read less",
"WROTE": "va escriure:",
"FROM": "des de",
"EMAIL": "correu electrònic",
"EMAIL_SUBJECT": "assumpte"
"FROM": "Des de",
"EMAIL": "Correu electrònic",
"EMAIL_SUBJECT": "Assumpte",
"PRIVATE": "Private note",
"TRANSCRIPT": "Transcript",
"CREATED_AT": "created {time}",
"UPDATED_AT": "updated {time}",
"SORT_BY": {
"RELEVANCE": "Relevance"
},
"DATE_RANGE": {
"LAST_7_DAYS": "Últims 7 dies",
"LAST_30_DAYS": "Últims 30 dies",
"LAST_60_DAYS": "Últims 60 dies",
"LAST_90_DAYS": "Últims 90 dies",
"CUSTOM_RANGE": "Custom range:",
"CREATED_BETWEEN": "Created between",
"AND": "and",
"APPLY": "Aplica",
"BEFORE_DATE": "Before {date}",
"AFTER_DATE": "After {date}",
"TIME_RANGE": "Filter by time",
"CLEAR_FILTER": "Esborra els filtres"
},
"FILTERS": {
"FILTER_MESSAGE": "Filter messages by:",
"FROM": "Remitent",
"IN": "Safata d'entrada",
"AGENTS": "Agents",
"CONTACTS": "Contactes",
"INBOXES": "Safates d'entrada",
"NO_AGENTS": "No s'han trobat agents",
"NO_CONTACTS": "Start by searching to see results",
"NO_INBOXES": "No inboxes found"
}
}
}
@@ -6,7 +6,8 @@
"OPTIONS": {
"NAME": "Název",
"DOMAIN": "Domain",
"CREATED_AT": "Vytvořeno"
"CREATED_AT": "Vytvořeno",
"CONTACTS_COUNT": "Contacts count"
}
},
"ORDER": {
@@ -28,6 +28,7 @@
"TYPES": {
"MEDIA": "Media",
"QUICK_REPLY": "Quick Reply",
"CALL_TO_ACTION": "Call to Action",
"TEXT": "Text"
}
},
@@ -275,6 +275,16 @@
"SIDEBAR": {
"CONTACT": "Contact",
"COPILOT": "Copilot"
},
"VOICE_WIDGET": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NOT_ANSWERED_YET": "Not answered yet",
"HANDLED_IN_ANOTHER_TAB": "Being handled in another tab",
"REJECT_CALL": "Reject",
"JOIN_CALL": "Join call",
"END_CALL": "End call"
}
},
"EMAIL_TRANSCRIPT": {
@@ -808,6 +808,35 @@
"LABEL": "Zpráva",
"PLACEHOLDER": "Please enter a message to show users with the form"
},
"BUTTON_TEXT": {
"LABEL": "Button text",
"PLACEHOLDER": "Please rate us"
},
"LANGUAGE": {
"LABEL": "Language",
"PLACEHOLDER": "Select template language"
},
"MESSAGE_PREVIEW": {
"LABEL": "Message preview",
"TOOLTIP": "This may vary slightly when rendered on WhatsApp's platform."
},
"TEMPLATE_STATUS": {
"APPROVED": "Approved by WhatsApp",
"PENDING": "Pending WhatsApp approval",
"REJECTED": "Meta rejected the template",
"DEFAULT": "Needs WhatsApp approval",
"NOT_FOUND": "The template does not exist in the Meta platform."
},
"TEMPLATE_CREATION": {
"SUCCESS_MESSAGE": "WhatsApp template created successfully and sent for approval",
"ERROR_MESSAGE": "Failed to create WhatsApp template"
},
"TEMPLATE_UPDATE_DIALOG": {
"TITLE": "Edit survey details",
"DESCRIPTION": "We will delete the previous template and make a new one which will be sent again for WhatsApp approval",
"CONFIRM": "Create new template",
"CANCEL": "Go back"
},
"SURVEY_RULE": {
"LABEL": "Survey rule",
"DESCRIPTION_PREFIX": "Send the survey if the conversation",
@@ -819,6 +848,7 @@
"SELECT_PLACEHOLDER": "select labels"
},
"NOTE": "Note: CSAT surveys are sent only once per conversation",
"WHATSAPP_NOTE": "Note: We will create a template and send it for WhatsApp approval. After being approved, surveys will be sent only once per conversation as per the survey rule.",
"API": {
"SUCCESS_MESSAGE": "CSAT settings updated successfully",
"ERROR_MESSAGE": "We couldn't update CSAT settings. Please try again later."
@@ -1,7 +1,7 @@
{
"SEARCH": {
"TABS": {
"ALL": "Vše",
"ALL": "All results",
"CONTACTS": "Kontakty",
"CONVERSATIONS": "Konverzace",
"MESSAGES": "Zprávy",
@@ -19,14 +19,50 @@
"LOADING_DATA": "Loading",
"EMPTY_STATE": "No {item} found for query '{query}'",
"EMPTY_STATE_FULL": "No results found for query '{query}'",
"PLACEHOLDER_KEYBINDING": "/ to focus",
"PLACEHOLDER_KEYBINDING": "/to focus",
"INPUT_PLACEHOLDER": "Search messages, contacts or conversations",
"RECENT_SEARCHES": "Recent searches",
"CLEAR_ALL": "Clear all",
"MOST_RECENT": "Most recent",
"EMPTY_STATE_DEFAULT": "Search by conversation id, email, phone number, messages for better search results.",
"BOT_LABEL": "Bot",
"READ_MORE": "Read more",
"READ_LESS": "Read less",
"WROTE": "wrote:",
"FROM": "od",
"EMAIL": "e-mailová adresa",
"EMAIL_SUBJECT": "předmět"
"FROM": "Od",
"EMAIL": "E-mailová adresa",
"EMAIL_SUBJECT": "Předmět",
"PRIVATE": "Private note",
"TRANSCRIPT": "Transcript",
"CREATED_AT": "created {time}",
"UPDATED_AT": "updated {time}",
"SORT_BY": {
"RELEVANCE": "Relevance"
},
"DATE_RANGE": {
"LAST_7_DAYS": "Posledních 7 dní",
"LAST_30_DAYS": "Posledních 30 dní",
"LAST_60_DAYS": "Posledních 60 dní",
"LAST_90_DAYS": "Posledních 90 dní",
"CUSTOM_RANGE": "Custom range:",
"CREATED_BETWEEN": "Created between",
"AND": "and",
"APPLY": "Použít",
"BEFORE_DATE": "Before {date}",
"AFTER_DATE": "After {date}",
"TIME_RANGE": "Filter by time",
"CLEAR_FILTER": "Clear filter"
},
"FILTERS": {
"FILTER_MESSAGE": "Filter messages by:",
"FROM": "Sender",
"IN": "Inbox",
"AGENTS": "Agenti",
"CONTACTS": "Kontakty",
"INBOXES": "Schránky",
"NO_AGENTS": "Nenalezeni žádní agenti",
"NO_CONTACTS": "Start by searching to see results",
"NO_INBOXES": "No inboxes found"
}
}
}
@@ -6,7 +6,8 @@
"OPTIONS": {
"NAME": "Navn",
"DOMAIN": "Domæne",
"CREATED_AT": "Oprettet den"
"CREATED_AT": "Oprettet den",
"CONTACTS_COUNT": "Contacts count"
}
},
"ORDER": {
@@ -28,6 +28,7 @@
"TYPES": {
"MEDIA": "Media",
"QUICK_REPLY": "Quick Reply",
"CALL_TO_ACTION": "Call to Action",
"TEXT": "Tekst"
}
},
@@ -275,6 +275,16 @@
"SIDEBAR": {
"CONTACT": "Kontakt",
"COPILOT": "Copilot"
},
"VOICE_WIDGET": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NOT_ANSWERED_YET": "Not answered yet",
"HANDLED_IN_ANOTHER_TAB": "Being handled in another tab",
"REJECT_CALL": "Reject",
"JOIN_CALL": "Join call",
"END_CALL": "End call"
}
},
"EMAIL_TRANSCRIPT": {
@@ -808,6 +808,35 @@
"LABEL": "Besked",
"PLACEHOLDER": "Please enter a message to show users with the form"
},
"BUTTON_TEXT": {
"LABEL": "Button text",
"PLACEHOLDER": "Please rate us"
},
"LANGUAGE": {
"LABEL": "Sprog",
"PLACEHOLDER": "Select template language"
},
"MESSAGE_PREVIEW": {
"LABEL": "Message preview",
"TOOLTIP": "This may vary slightly when rendered on WhatsApp's platform."
},
"TEMPLATE_STATUS": {
"APPROVED": "Approved by WhatsApp",
"PENDING": "Pending WhatsApp approval",
"REJECTED": "Meta rejected the template",
"DEFAULT": "Needs WhatsApp approval",
"NOT_FOUND": "The template does not exist in the Meta platform."
},
"TEMPLATE_CREATION": {
"SUCCESS_MESSAGE": "WhatsApp template created successfully and sent for approval",
"ERROR_MESSAGE": "Failed to create WhatsApp template"
},
"TEMPLATE_UPDATE_DIALOG": {
"TITLE": "Edit survey details",
"DESCRIPTION": "We will delete the previous template and make a new one which will be sent again for WhatsApp approval",
"CONFIRM": "Create new template",
"CANCEL": "Gå tilbage"
},
"SURVEY_RULE": {
"LABEL": "Survey rule",
"DESCRIPTION_PREFIX": "Send the survey if the conversation",
@@ -819,6 +848,7 @@
"SELECT_PLACEHOLDER": "select labels"
},
"NOTE": "Note: CSAT surveys are sent only once per conversation",
"WHATSAPP_NOTE": "Note: We will create a template and send it for WhatsApp approval. After being approved, surveys will be sent only once per conversation as per the survey rule.",
"API": {
"SUCCESS_MESSAGE": "CSAT settings updated successfully",
"ERROR_MESSAGE": "We couldn't update CSAT settings. Please try again later."
@@ -1,7 +1,7 @@
{
"SEARCH": {
"TABS": {
"ALL": "Alle",
"ALL": "All results",
"CONTACTS": "Kontakter",
"CONVERSATIONS": "Samtaler",
"MESSAGES": "Beskeder",
@@ -19,14 +19,50 @@
"LOADING_DATA": "Loading",
"EMPTY_STATE": "No {item} found for query '{query}'",
"EMPTY_STATE_FULL": "No results found for query '{query}'",
"PLACEHOLDER_KEYBINDING": "/ to focus",
"PLACEHOLDER_KEYBINDING": "/to focus",
"INPUT_PLACEHOLDER": "Search messages, contacts or conversations",
"RECENT_SEARCHES": "Recent searches",
"CLEAR_ALL": "Clear all",
"MOST_RECENT": "Most recent",
"EMPTY_STATE_DEFAULT": "Search by conversation id, email, phone number, messages for better search results.",
"BOT_LABEL": "Bot",
"READ_MORE": "Read more",
"READ_LESS": "Read less",
"WROTE": "wrote:",
"FROM": "fra",
"EMAIL": "e-mail",
"EMAIL_SUBJECT": "emne"
"FROM": "Fra",
"EMAIL": "E-mail",
"EMAIL_SUBJECT": "Emne",
"PRIVATE": "Private note",
"TRANSCRIPT": "Transcript",
"CREATED_AT": "created {time}",
"UPDATED_AT": "updated {time}",
"SORT_BY": {
"RELEVANCE": "Relevance"
},
"DATE_RANGE": {
"LAST_7_DAYS": "Seneste 7 dage",
"LAST_30_DAYS": "Seneste 30 dage",
"LAST_60_DAYS": "Seneste 60 dage",
"LAST_90_DAYS": "Seneste 90 dage",
"CUSTOM_RANGE": "Custom range:",
"CREATED_BETWEEN": "Created between",
"AND": "and",
"APPLY": "Anvend",
"BEFORE_DATE": "Before {date}",
"AFTER_DATE": "After {date}",
"TIME_RANGE": "Filter by time",
"CLEAR_FILTER": "Clear filter"
},
"FILTERS": {
"FILTER_MESSAGE": "Filter messages by:",
"FROM": "Afsender",
"IN": "Indbakke",
"AGENTS": "Agenter",
"CONTACTS": "Kontakter",
"INBOXES": "Indbakker",
"NO_AGENTS": "Ingen agenter fundet",
"NO_CONTACTS": "Start by searching to see results",
"NO_INBOXES": "No inboxes found"
}
}
}
@@ -6,7 +6,8 @@
"OPTIONS": {
"NAME": "Name",
"DOMAIN": "Domain",
"CREATED_AT": "Erstellt am"
"CREATED_AT": "Erstellt am",
"CONTACTS_COUNT": "Contacts count"
}
},
"ORDER": {
@@ -28,6 +28,7 @@
"TYPES": {
"MEDIA": "Media",
"QUICK_REPLY": "Quick Reply",
"CALL_TO_ACTION": "Call to Action",
"TEXT": "Text"
}
},
@@ -275,6 +275,16 @@
"SIDEBAR": {
"CONTACT": "Kontakt",
"COPILOT": "Copilot"
},
"VOICE_WIDGET": {
"INCOMING_CALL": "Eingehender Anruf",
"OUTGOING_CALL": "Ausgehender Anruf",
"CALL_IN_PROGRESS": "Anruf läuft",
"NOT_ANSWERED_YET": "Noch nicht beantwortet",
"HANDLED_IN_ANOTHER_TAB": "Being handled in another tab",
"REJECT_CALL": "Reject",
"JOIN_CALL": "Join call",
"END_CALL": "End call"
}
},
"EMAIL_TRANSCRIPT": {
@@ -808,6 +808,35 @@
"LABEL": "Nachricht",
"PLACEHOLDER": "Please enter a message to show users with the form"
},
"BUTTON_TEXT": {
"LABEL": "Button text",
"PLACEHOLDER": "Please rate us"
},
"LANGUAGE": {
"LABEL": "Sprache",
"PLACEHOLDER": "Select template language"
},
"MESSAGE_PREVIEW": {
"LABEL": "Message preview",
"TOOLTIP": "This may vary slightly when rendered on WhatsApp's platform."
},
"TEMPLATE_STATUS": {
"APPROVED": "Approved by WhatsApp",
"PENDING": "Pending WhatsApp approval",
"REJECTED": "Meta rejected the template",
"DEFAULT": "Needs WhatsApp approval",
"NOT_FOUND": "The template does not exist in the Meta platform."
},
"TEMPLATE_CREATION": {
"SUCCESS_MESSAGE": "WhatsApp template created successfully and sent for approval",
"ERROR_MESSAGE": "Failed to create WhatsApp template"
},
"TEMPLATE_UPDATE_DIALOG": {
"TITLE": "Edit survey details",
"DESCRIPTION": "We will delete the previous template and make a new one which will be sent again for WhatsApp approval",
"CONFIRM": "Create new template",
"CANCEL": "Zurück"
},
"SURVEY_RULE": {
"LABEL": "Survey rule",
"DESCRIPTION_PREFIX": "Send the survey if the conversation",
@@ -819,6 +848,7 @@
"SELECT_PLACEHOLDER": "select labels"
},
"NOTE": "Note: CSAT surveys are sent only once per conversation",
"WHATSAPP_NOTE": "Note: We will create a template and send it for WhatsApp approval. After being approved, surveys will be sent only once per conversation as per the survey rule.",
"API": {
"SUCCESS_MESSAGE": "CSAT settings updated successfully",
"ERROR_MESSAGE": "We couldn't update CSAT settings. Please try again later."
@@ -1,7 +1,7 @@
{
"SEARCH": {
"TABS": {
"ALL": "Alle",
"ALL": "All results",
"CONTACTS": "Kontakte",
"CONVERSATIONS": "Gespräche",
"MESSAGES": "Nachrichten",
@@ -19,14 +19,50 @@
"LOADING_DATA": "Wird geladen",
"EMPTY_STATE": "Keine {item} für Abfrage '{query} ' gefunden",
"EMPTY_STATE_FULL": "Kein Ergebnis für Abfrage '{query} ' gefunden",
"PLACEHOLDER_KEYBINDING": "/ fokussieren",
"PLACEHOLDER_KEYBINDING": "/fokussieren",
"INPUT_PLACEHOLDER": "Search messages, contacts or conversations",
"RECENT_SEARCHES": "Recent searches",
"CLEAR_ALL": "Alle löschen",
"MOST_RECENT": "Most recent",
"EMPTY_STATE_DEFAULT": "Search by conversation id, email, phone number, messages for better search results.",
"BOT_LABEL": "Bot",
"READ_MORE": "Mehr erfahren",
"READ_LESS": "Read less",
"WROTE": "schrieb:",
"FROM": "von",
"EMAIL": "e-Mail",
"EMAIL_SUBJECT": "betreff"
"FROM": "Von",
"EMAIL": "E-Mail",
"EMAIL_SUBJECT": "Betreff",
"PRIVATE": "Private note",
"TRANSCRIPT": "Transcript",
"CREATED_AT": "erstellt am {time}",
"UPDATED_AT": "updated {time}",
"SORT_BY": {
"RELEVANCE": "Relevance"
},
"DATE_RANGE": {
"LAST_7_DAYS": "Letzte 7 Tage",
"LAST_30_DAYS": "Letzte 30 Tage",
"LAST_60_DAYS": "Letzten 60 Tage",
"LAST_90_DAYS": "Letzten 90 Tage",
"CUSTOM_RANGE": "Custom range:",
"CREATED_BETWEEN": "Created between",
"AND": "und",
"APPLY": "Übernehmen",
"BEFORE_DATE": "Before {date}",
"AFTER_DATE": "After {date}",
"TIME_RANGE": "Filter by time",
"CLEAR_FILTER": "Filter löschen"
},
"FILTERS": {
"FILTER_MESSAGE": "Filter messages by:",
"FROM": "Absender",
"IN": "Posteingang",
"AGENTS": "Agenten",
"CONTACTS": "Kontakte",
"INBOXES": "Posteingänge",
"NO_AGENTS": "Keine Agenten gefunden",
"NO_CONTACTS": "Start by searching to see results",
"NO_INBOXES": "No inboxes found"
}
}
}
@@ -6,7 +6,8 @@
"OPTIONS": {
"NAME": "Όνομα",
"DOMAIN": "Domain",
"CREATED_AT": "Δημιουργήθηκε στις"
"CREATED_AT": "Δημιουργήθηκε στις",
"CONTACTS_COUNT": "Contacts count"
}
},
"ORDER": {
@@ -28,6 +28,7 @@
"TYPES": {
"MEDIA": "Media",
"QUICK_REPLY": "Quick Reply",
"CALL_TO_ACTION": "Call to Action",
"TEXT": "Κείμενο"
}
},
@@ -275,6 +275,16 @@
"SIDEBAR": {
"CONTACT": "Επαφές",
"COPILOT": "Copilot"
},
"VOICE_WIDGET": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NOT_ANSWERED_YET": "Not answered yet",
"HANDLED_IN_ANOTHER_TAB": "Being handled in another tab",
"REJECT_CALL": "Reject",
"JOIN_CALL": "Join call",
"END_CALL": "End call"
}
},
"EMAIL_TRANSCRIPT": {
@@ -808,6 +808,35 @@
"LABEL": "Μήνυμα",
"PLACEHOLDER": "Please enter a message to show users with the form"
},
"BUTTON_TEXT": {
"LABEL": "Button text",
"PLACEHOLDER": "Please rate us"
},
"LANGUAGE": {
"LABEL": "Γλώσσα",
"PLACEHOLDER": "Select template language"
},
"MESSAGE_PREVIEW": {
"LABEL": "Message preview",
"TOOLTIP": "This may vary slightly when rendered on WhatsApp's platform."
},
"TEMPLATE_STATUS": {
"APPROVED": "Approved by WhatsApp",
"PENDING": "Pending WhatsApp approval",
"REJECTED": "Meta rejected the template",
"DEFAULT": "Needs WhatsApp approval",
"NOT_FOUND": "The template does not exist in the Meta platform."
},
"TEMPLATE_CREATION": {
"SUCCESS_MESSAGE": "WhatsApp template created successfully and sent for approval",
"ERROR_MESSAGE": "Failed to create WhatsApp template"
},
"TEMPLATE_UPDATE_DIALOG": {
"TITLE": "Edit survey details",
"DESCRIPTION": "We will delete the previous template and make a new one which will be sent again for WhatsApp approval",
"CONFIRM": "Create new template",
"CANCEL": "Πίσω"
},
"SURVEY_RULE": {
"LABEL": "Survey rule",
"DESCRIPTION_PREFIX": "Send the survey if the conversation",
@@ -819,6 +848,7 @@
"SELECT_PLACEHOLDER": "select labels"
},
"NOTE": "Note: CSAT surveys are sent only once per conversation",
"WHATSAPP_NOTE": "Note: We will create a template and send it for WhatsApp approval. After being approved, surveys will be sent only once per conversation as per the survey rule.",
"API": {
"SUCCESS_MESSAGE": "CSAT settings updated successfully",
"ERROR_MESSAGE": "We couldn't update CSAT settings. Please try again later."
@@ -1,7 +1,7 @@
{
"SEARCH": {
"TABS": {
"ALL": "Όλες",
"ALL": "All results",
"CONTACTS": "Επαφές",
"CONVERSATIONS": "Συζητήσεις",
"MESSAGES": "Μηνύματα",
@@ -19,14 +19,50 @@
"LOADING_DATA": "Loading",
"EMPTY_STATE": "No {item} found for query '{query}'",
"EMPTY_STATE_FULL": "No results found for query '{query}'",
"PLACEHOLDER_KEYBINDING": "/ to focus",
"PLACEHOLDER_KEYBINDING": "/to focus",
"INPUT_PLACEHOLDER": "Search messages, contacts or conversations",
"RECENT_SEARCHES": "Recent searches",
"CLEAR_ALL": "Clear all",
"MOST_RECENT": "Most recent",
"EMPTY_STATE_DEFAULT": "Search by conversation id, email, phone number, messages for better search results.",
"BOT_LABEL": "Bot",
"READ_MORE": "Read more",
"READ_LESS": "Read less",
"WROTE": "wrote:",
"FROM": "από",
"EMAIL": "email",
"EMAIL_SUBJECT": "θέμα"
"FROM": "Από",
"EMAIL": "Email",
"EMAIL_SUBJECT": "Θέμα",
"PRIVATE": "Private note",
"TRANSCRIPT": "Transcript",
"CREATED_AT": "created {time}",
"UPDATED_AT": "updated {time}",
"SORT_BY": {
"RELEVANCE": "Relevance"
},
"DATE_RANGE": {
"LAST_7_DAYS": "Τελευταίες 7 ημέρες",
"LAST_30_DAYS": "Τελευταίες 30 ημέρες",
"LAST_60_DAYS": "Τελευταίες 60 ημέρες",
"LAST_90_DAYS": "Τελευταίες 90 ημέρες",
"CUSTOM_RANGE": "Custom range:",
"CREATED_BETWEEN": "Created between",
"AND": "και",
"APPLY": "Εφαρμογή",
"BEFORE_DATE": "Before {date}",
"AFTER_DATE": "After {date}",
"TIME_RANGE": "Filter by time",
"CLEAR_FILTER": "Clear filter"
},
"FILTERS": {
"FILTER_MESSAGE": "Filter messages by:",
"FROM": "Αποστολέας",
"IN": "Εισερχόμενα",
"AGENTS": "Πράκτορες",
"CONTACTS": "Επαφές",
"INBOXES": "Κιβώτια Εισερχομένων",
"NO_AGENTS": "Δεν βρέθηκαν Πράκτορες",
"NO_CONTACTS": "Start by searching to see results",
"NO_INBOXES": "No inboxes found"
}
}
}
@@ -6,7 +6,8 @@
"OPTIONS": {
"NAME": "Name",
"DOMAIN": "Domain",
"CREATED_AT": "Created at"
"CREATED_AT": "Created at",
"CONTACTS_COUNT": "Contacts count"
}
},
"ORDER": {
@@ -808,6 +808,35 @@
"LABEL": "Message",
"PLACEHOLDER": "Please enter a message to show users with the form"
},
"BUTTON_TEXT": {
"LABEL": "Button text",
"PLACEHOLDER": "Please rate us"
},
"LANGUAGE": {
"LABEL": "Language",
"PLACEHOLDER": "Select template language"
},
"MESSAGE_PREVIEW": {
"LABEL": "Message preview",
"TOOLTIP": "This may vary slightly when rendered on WhatsApp's platform."
},
"TEMPLATE_STATUS": {
"APPROVED": "Approved by WhatsApp",
"PENDING": "Pending WhatsApp approval",
"REJECTED": "Meta rejected the template",
"DEFAULT": "Needs WhatsApp approval",
"NOT_FOUND": "The template does not exist in the Meta platform."
},
"TEMPLATE_CREATION": {
"SUCCESS_MESSAGE": "WhatsApp template created successfully and sent for approval",
"ERROR_MESSAGE": "Failed to create WhatsApp template"
},
"TEMPLATE_UPDATE_DIALOG": {
"TITLE": "Edit survey details",
"DESCRIPTION": "We will delete the previous template and make a new one which will be sent again for WhatsApp approval",
"CONFIRM": "Create new template",
"CANCEL": "Go back"
},
"SURVEY_RULE": {
"LABEL": "Survey rule",
"DESCRIPTION_PREFIX": "Send the survey if the conversation",
@@ -819,6 +848,7 @@
"SELECT_PLACEHOLDER": "select labels"
},
"NOTE": "Note: CSAT surveys are sent only once per conversation",
"WHATSAPP_NOTE": "Note: We will create a template and send it for WhatsApp approval. After being approved, surveys will be sent only once per conversation as per the survey rule.",
"API": {
"SUCCESS_MESSAGE": "CSAT settings updated successfully",
"ERROR_MESSAGE": "We couldn't update CSAT settings. Please try again later."
@@ -1,7 +1,7 @@
{
"SEARCH": {
"TABS": {
"ALL": "All",
"ALL": "All results",
"CONTACTS": "Contacts",
"CONVERSATIONS": "Conversations",
"MESSAGES": "Messages",
@@ -19,14 +19,50 @@
"LOADING_DATA": "Loading",
"EMPTY_STATE": "No {item} found for query '{query}'",
"EMPTY_STATE_FULL": "No results found for query '{query}'",
"PLACEHOLDER_KEYBINDING": "/ to focus",
"PLACEHOLDER_KEYBINDING": "/to focus",
"INPUT_PLACEHOLDER": "Type 3 or more characters to search",
"RECENT_SEARCHES": "Recent searches",
"CLEAR_ALL": "Clear all",
"MOST_RECENT": "Most recent",
"EMPTY_STATE_DEFAULT": "Search by conversation id, email, phone number, messages for better search results. ",
"BOT_LABEL": "Bot",
"READ_MORE": "Read more",
"READ_LESS": "Read less",
"WROTE": "wrote:",
"FROM": "from",
"EMAIL": "email",
"EMAIL_SUBJECT": "subject"
"FROM": "From",
"EMAIL": "Email",
"EMAIL_SUBJECT": "Subject",
"PRIVATE": "Private note",
"TRANSCRIPT": "Transcript",
"CREATED_AT": "created {time}",
"UPDATED_AT": "updated {time}",
"SORT_BY": {
"RELEVANCE": "Relevance"
},
"DATE_RANGE": {
"LAST_7_DAYS": "Last 7 days",
"LAST_30_DAYS": "Last 30 days",
"LAST_60_DAYS": "Last 60 days",
"LAST_90_DAYS": "Last 90 days",
"CUSTOM_RANGE": "Custom range:",
"CREATED_BETWEEN": "Created between",
"AND": "and",
"APPLY": "Apply",
"BEFORE_DATE": "Before {date}",
"AFTER_DATE": "After {date}",
"TIME_RANGE": "Filter by time",
"CLEAR_FILTER": "Clear filter"
},
"FILTERS": {
"FILTER_MESSAGE": "Filter messages by:",
"FROM": "Sender",
"IN": "Inbox",
"AGENTS": "Agents",
"CONTACTS": "Contacts",
"INBOXES": "Inboxes",
"NO_AGENTS": "No agents found",
"NO_CONTACTS": "Start by searching to see results",
"NO_INBOXES": "No inboxes found"
}
}
}
@@ -132,7 +132,7 @@
},
"BADGES": {
"PRE_CHAT": "Pre-chat",
"RESOLUTION": "Resolution"
"RESOLUTION": "Resolución"
}
}
}
@@ -104,7 +104,7 @@
"CONTENT": "Contacto compartido"
},
"embed": {
"CONTENT": "Embedded content"
"CONTENT": "Contenido incrustado"
}
},
"CHAT_SORT_BY_FILTER": {
@@ -1,32 +1,33 @@
{
"COMPANIES": {
"HEADER": "Companies",
"HEADER": "Empresas",
"SORT_BY": {
"LABEL": "Ordenar por",
"OPTIONS": {
"NAME": "Nombre",
"DOMAIN": "Dominio",
"CREATED_AT": "Creado el"
"CREATED_AT": "Creado el",
"CONTACTS_COUNT": "Contacts count"
}
},
"ORDER": {
"LABEL": "Order",
"LABEL": "Orden",
"OPTIONS": {
"ASCENDING": "Ascendente",
"DESCENDING": "Descendente"
}
},
"SEARCH_PLACEHOLDER": "Search companies...",
"LOADING": "Loading companies...",
"UNNAMED": "Unnamed Company",
"CONTACTS_COUNT": "{n} contact | {n} contacts",
"SEARCH_PLACEHOLDER": "Buscar empresas...",
"LOADING": "Cargando empresas...",
"UNNAMED": "Empresa sin nombre",
"CONTACTS_COUNT": "{n} contacto | {n} contactos",
"EMPTY_STATE": {
"TITLE": "No companies found"
"TITLE": "No se encontraron empresas"
}
},
"COMPANIES_LAYOUT": {
"PAGINATION_FOOTER": {
"SHOWING": "Showing {startItem} {endItem} of {totalItems} company | Showing {startItem} {endItem} of {totalItems} companies"
"SHOWING": "Mostrando {startItem} {endItem} de {totalItems} empresa | Mostrando {startItem} {endItem} de {totalItems} empresas"
}
}
}
@@ -458,7 +458,7 @@
"PLACEHOLDER": "Agregar Instagram"
},
"TIKTOK": {
"PLACEHOLDER": "Add TikTok"
"PLACEHOLDER": "Añadir TikTok"
},
"LINKEDIN": {
"PLACEHOLDER": "Agregar LinkedIn"
@@ -28,6 +28,7 @@
"TYPES": {
"MEDIA": "Media",
"QUICK_REPLY": "Quick Reply",
"CALL_TO_ACTION": "Call to Action",
"TEXT": "Texto"
}
},
@@ -275,6 +275,16 @@
"SIDEBAR": {
"CONTACT": "Contacto",
"COPILOT": "Copilot"
},
"VOICE_WIDGET": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NOT_ANSWERED_YET": "Not answered yet",
"HANDLED_IN_ANOTHER_TAB": "Atendido en otra pestaña",
"REJECT_CALL": "Rechazar",
"JOIN_CALL": "Unirse a la llamada",
"END_CALL": "Terminar llamada"
}
},
"EMAIL_TRANSCRIPT": {
@@ -808,6 +808,35 @@
"LABEL": "Mensaje",
"PLACEHOLDER": "Please enter a message to show users with the form"
},
"BUTTON_TEXT": {
"LABEL": "Button text",
"PLACEHOLDER": "Please rate us"
},
"LANGUAGE": {
"LABEL": "Idioma",
"PLACEHOLDER": "Select template language"
},
"MESSAGE_PREVIEW": {
"LABEL": "Message preview",
"TOOLTIP": "This may vary slightly when rendered on WhatsApp's platform."
},
"TEMPLATE_STATUS": {
"APPROVED": "Approved by WhatsApp",
"PENDING": "Pending WhatsApp approval",
"REJECTED": "Meta rejected the template",
"DEFAULT": "Needs WhatsApp approval",
"NOT_FOUND": "The template does not exist in the Meta platform."
},
"TEMPLATE_CREATION": {
"SUCCESS_MESSAGE": "WhatsApp template created successfully and sent for approval",
"ERROR_MESSAGE": "Failed to create WhatsApp template"
},
"TEMPLATE_UPDATE_DIALOG": {
"TITLE": "Edit survey details",
"DESCRIPTION": "We will delete the previous template and make a new one which will be sent again for WhatsApp approval",
"CONFIRM": "Create new template",
"CANCEL": "Volver"
},
"SURVEY_RULE": {
"LABEL": "Survey rule",
"DESCRIPTION_PREFIX": "Send the survey if the conversation",
@@ -819,6 +848,7 @@
"SELECT_PLACEHOLDER": "select labels"
},
"NOTE": "Note: CSAT surveys are sent only once per conversation",
"WHATSAPP_NOTE": "Note: We will create a template and send it for WhatsApp approval. After being approved, surveys will be sent only once per conversation as per the survey rule.",
"API": {
"SUCCESS_MESSAGE": "CSAT settings updated successfully",
"ERROR_MESSAGE": "We couldn't update CSAT settings. Please try again later."
@@ -1,7 +1,7 @@
{
"SEARCH": {
"TABS": {
"ALL": "Todos",
"ALL": "All results",
"CONTACTS": "Contactos",
"CONVERSATIONS": "Conversaciones",
"MESSAGES": "Mensajes",
@@ -19,14 +19,50 @@
"LOADING_DATA": "Cargando",
"EMPTY_STATE": "Ningún {item} encontrado para la consulta '{query}'",
"EMPTY_STATE_FULL": "No se han encontrado resultados para la consulta '{query}'",
"PLACEHOLDER_KEYBINDING": "/ para enfocar",
"PLACEHOLDER_KEYBINDING": "/para enfocar",
"INPUT_PLACEHOLDER": "Buscar mensajes, contactos o conversaciones",
"RECENT_SEARCHES": "Recent searches",
"CLEAR_ALL": "Limpiar todo",
"MOST_RECENT": "Most recent",
"EMPTY_STATE_DEFAULT": "Buscar por Id de conversación, correo electrónico, número de teléfono, mensajes para mejores resultados de búsqueda.",
"BOT_LABEL": "Bot",
"READ_MORE": "Leer más",
"READ_LESS": "Read less",
"WROTE": "escribió:",
"FROM": "De",
"EMAIL": "email",
"EMAIL_SUBJECT": "asunto"
"FROM": "Desde",
"EMAIL": "E-mail",
"EMAIL_SUBJECT": "Asunto",
"PRIVATE": "Private note",
"TRANSCRIPT": "Transcript",
"CREATED_AT": "creado {time}",
"UPDATED_AT": "updated {time}",
"SORT_BY": {
"RELEVANCE": "Relevance"
},
"DATE_RANGE": {
"LAST_7_DAYS": "Últimos 7 días",
"LAST_30_DAYS": "Últimos 30 días",
"LAST_60_DAYS": "Últimos 60 días",
"LAST_90_DAYS": "Últimos 90 días",
"CUSTOM_RANGE": "Custom range:",
"CREATED_BETWEEN": "Created between",
"AND": "y",
"APPLY": "Aplicar",
"BEFORE_DATE": "Before {date}",
"AFTER_DATE": "After {date}",
"TIME_RANGE": "Filter by time",
"CLEAR_FILTER": "Limpiar filtros"
},
"FILTERS": {
"FILTER_MESSAGE": "Filter messages by:",
"FROM": "Remitente",
"IN": "Bandeja de entrada",
"AGENTS": "Agentes",
"CONTACTS": "Contactos",
"INBOXES": "Entradas",
"NO_AGENTS": "No se encontraron agentes",
"NO_CONTACTS": "Start by searching to see results",
"NO_INBOXES": "No inboxes found"
}
}
}
@@ -234,7 +234,7 @@
"CONTACT_SUPPORT": "Contactar a soporte",
"SELECTOR_SUBTITLE": "Seleccione una cuenta de la siguiente lista",
"PROFILE_SETTINGS": "Ajustes del perfil",
"YEAR_IN_REVIEW": "Year in Review",
"YEAR_IN_REVIEW": "Resumen del año",
"KEYBOARD_SHORTCUTS": "Atajos de teclado",
"APPEARANCE": "Cambiar apariencia",
"SUPER_ADMIN_CONSOLE": "Consola SuperAdmin",
@@ -307,7 +307,7 @@
"SETTINGS": "Ajustes",
"CONTACTS": "Contactos",
"ACTIVE": "Activo",
"COMPANIES": "Companies",
"COMPANIES": "Empresas",
"ALL_COMPANIES": "All Companies",
"CAPTAIN": "Capitán",
"CAPTAIN_ASSISTANTS": "Asistentes",
@@ -1,62 +1,62 @@
{
"YEAR_IN_REVIEW": {
"TITLE": "Year in Review",
"LOADING": "Loading your year in review...",
"ERROR": "Failed to load year in review",
"TITLE": "Resumen del año",
"LOADING": "Cargando tu resumen del año...",
"ERROR": "No se pudo cargar el resumen del año",
"CLOSE": "Cerrar",
"CONVERSATIONS": {
"TITLE": "You have handled",
"TITLE": "Has gestionado",
"SUBTITLE": "conversaciones",
"FALLBACK": "This year wasn't about the numbers. It was about showing up.",
"FALLBACK": "Este año no se trató de los números. Se trató de estar presente.",
"COMPARISON": {
"0_50": "You showed up, and that's how every good inbox begins.",
"50_100": "You kept the replies flowing and the conversations alive.",
"100_500": "You handled serious volume and kept everything on track.",
"500_2000": "You kept things moving while the volume kept climbing.",
"2000_10000": "You ran high traffic through your inbox without breaking a sweat.",
"10000_PLUS": "That's a full city of customers knocking on your door. You made it look effortless."
"0_50": "Estuviste ahí, y así comienza toda buena bandeja de entrada.",
"50_100": "Mantuviste las respuestas fluyendo y las conversaciones vivas.",
"100_500": "Gestionaste un volumen considerable y mantuviste todo bajo control.",
"500_2000": "Hiciste que todo siguiera avanzando mientras el volumen no dejaba de crecer.",
"2000_10000": "Manejaste un alto tráfico en tu bandeja de entrada sin despeinarte.",
"10000_PLUS": "Una ciudad entera de clientes tocando a tu puerta. Y lo hiciste parecer fácil."
}
},
"BUSIEST_DAY": {
"TITLE": "Your busiest day was",
"MESSAGE": "{count} conversations that day.",
"TITLE": "Tu día más ocupado fue",
"MESSAGE": "{count} conversaciones ese día.",
"COMPARISON": {
"0_5": "A warm-up lap that barely woke the inbox.",
"5_10": "Enough action to justify a second cup of coffee.",
"10_25": "Things got busy and the inbox stayed on its toes.",
"25_50": "A proper rush that barely broke a sweat.",
"50_100": "Controlled chaos, handled like a normal Tuesday.",
"100_500": "Absolute dumpster fire, somehow still shipping replies.",
"500_PLUS": "The inbox lost all chill and never slowed down."
"0_5": "Un calentamiento que apenas despertó la bandeja de entrada.",
"5_10": "Suficiente movimiento como para justificar una segunda taza de café.",
"10_25": "La cosa se puso intensa y la bandeja de entrada se mantuvo alerta.",
"25_50": "Un buen pico de actividad que apenas hizo sudar.",
"50_100": "Caos controlado, gestionado como un martes cualquiera.",
"100_500": "Caos absoluto, y aun así las respuestas siguieron saliendo.",
"500_PLUS": "La bandeja de entrada perdió toda la calma y no se detuvo en ningún momento."
}
},
"PERSONALITY": {
"TITLE": "Your support personality is",
"TITLE": "Tu estilo de soporte es",
"MESSAGES": {
"SWIFT_HELPER": "You replied in {time} on average. Faster than most notifications.",
"QUICK_RESPONDER": "You replied in {time} on average. The inbox barely waited.",
"STEADY_SUPPORT": "You replied in {time} on average. Calm pace, solid replies.",
"THOUGHTFUL_ADVISOR": "You replied in {time} on average. Took the time to get it right."
"SWIFT_HELPER": "Respondías en {time} de media. Más rápido que la mayoría de las notificaciones.",
"QUICK_RESPONDER": "Respondías en {time} de media. La bandeja de entrada casi no esperó.",
"STEADY_SUPPORT": "Respondías en {time} de media. Ritmo constante y respuestas sólidas.",
"THOUGHTFUL_ADVISOR": "Respondías en {time} de media. Te tomaste el tiempo para hacerlo bien."
}
},
"THANK_YOU": {
"TITLE": "Congratulations on surviving the inbox of {year}.",
"MESSAGE": "Thank you for your incredible dedication to supporting customers throughout this year. Your hard work has made a real difference, and we're grateful to have you on this journey. Here's to making {nextYear} even better together!"
"TITLE": "Felicidades por sobrevivir a la bandeja de entrada del {year}.",
"MESSAGE": "Gracias por tu increíble dedicación al soporte de clientes durante este año. Tu trabajo ha marcado una diferencia real y estamos agradecidos de tenerte en este camino. ¡Vamos a hacer que {nextYear} sea aún mejor juntos!"
},
"SHARE_MODAL": {
"TITLE": "Share Your Year in Review",
"PREPARING": "Preparing your image...",
"TITLE": "Comparte tu resumen del año",
"PREPARING": "Preparando tu imagen...",
"DOWNLOAD": "Descargar",
"SHARE_TITLE": "My {year} Year in Review",
"SHARE_TEXT": "Check out my {year} Year in Review with Chatwoot!",
"BRANDING": "Made with Chatwoot"
"SHARE_TITLE": "Mi resumen del año {year}",
"SHARE_TEXT": "¡Mira mi resumen del año {year} con Chatwoot!",
"BRANDING": "Hecho con Chatwoot"
},
"BANNER": {
"TITLE": "Your {year} Year in Review is here",
"BUTTON": "See your impact"
"TITLE": "Tu resumen del año {year} ya está aquí",
"BUTTON": "Ver tu impacto"
},
"NAVIGATION": {
"PREVIOUS": "Previous",
"PREVIOUS": "Anterior",
"NEXT": "Siguiente",
"SHARE": "Compartir la conversación"
}
@@ -6,7 +6,8 @@
"OPTIONS": {
"NAME": "Name",
"DOMAIN": "Domain",
"CREATED_AT": "Created at"
"CREATED_AT": "Created at",
"CONTACTS_COUNT": "Contacts count"
}
},
"ORDER": {
@@ -28,6 +28,7 @@
"TYPES": {
"MEDIA": "Media",
"QUICK_REPLY": "Quick Reply",
"CALL_TO_ACTION": "Call to Action",
"TEXT": "Text"
}
},
@@ -275,6 +275,16 @@
"SIDEBAR": {
"CONTACT": "Contact",
"COPILOT": "Copilot"
},
"VOICE_WIDGET": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NOT_ANSWERED_YET": "Not answered yet",
"HANDLED_IN_ANOTHER_TAB": "Being handled in another tab",
"REJECT_CALL": "Reject",
"JOIN_CALL": "Join call",
"END_CALL": "End call"
}
},
"EMAIL_TRANSCRIPT": {
@@ -808,6 +808,35 @@
"LABEL": "Message",
"PLACEHOLDER": "Please enter a message to show users with the form"
},
"BUTTON_TEXT": {
"LABEL": "Button text",
"PLACEHOLDER": "Please rate us"
},
"LANGUAGE": {
"LABEL": "Language",
"PLACEHOLDER": "Select template language"
},
"MESSAGE_PREVIEW": {
"LABEL": "Message preview",
"TOOLTIP": "This may vary slightly when rendered on WhatsApp's platform."
},
"TEMPLATE_STATUS": {
"APPROVED": "Approved by WhatsApp",
"PENDING": "Pending WhatsApp approval",
"REJECTED": "Meta rejected the template",
"DEFAULT": "Needs WhatsApp approval",
"NOT_FOUND": "The template does not exist in the Meta platform."
},
"TEMPLATE_CREATION": {
"SUCCESS_MESSAGE": "WhatsApp template created successfully and sent for approval",
"ERROR_MESSAGE": "Failed to create WhatsApp template"
},
"TEMPLATE_UPDATE_DIALOG": {
"TITLE": "Edit survey details",
"DESCRIPTION": "We will delete the previous template and make a new one which will be sent again for WhatsApp approval",
"CONFIRM": "Create new template",
"CANCEL": "Go back"
},
"SURVEY_RULE": {
"LABEL": "Survey rule",
"DESCRIPTION_PREFIX": "Send the survey if the conversation",
@@ -819,6 +848,7 @@
"SELECT_PLACEHOLDER": "select labels"
},
"NOTE": "Note: CSAT surveys are sent only once per conversation",
"WHATSAPP_NOTE": "Note: We will create a template and send it for WhatsApp approval. After being approved, surveys will be sent only once per conversation as per the survey rule.",
"API": {
"SUCCESS_MESSAGE": "CSAT settings updated successfully",
"ERROR_MESSAGE": "We couldn't update CSAT settings. Please try again later."
@@ -1,7 +1,7 @@
{
"SEARCH": {
"TABS": {
"ALL": "All",
"ALL": "All results",
"CONTACTS": "Contacts",
"CONVERSATIONS": "Conversations",
"MESSAGES": "Messages",
@@ -19,14 +19,50 @@
"LOADING_DATA": "Loading",
"EMPTY_STATE": "No {item} found for query '{query}'",
"EMPTY_STATE_FULL": "No results found for query '{query}'",
"PLACEHOLDER_KEYBINDING": "/ to focus",
"PLACEHOLDER_KEYBINDING": "/to focus",
"INPUT_PLACEHOLDER": "Type 3 or more characters to search",
"RECENT_SEARCHES": "Recent searches",
"CLEAR_ALL": "Clear all",
"MOST_RECENT": "Most recent",
"EMPTY_STATE_DEFAULT": "Search by conversation id, email, phone number, messages for better search results. ",
"BOT_LABEL": "Bot",
"READ_MORE": "Read more",
"READ_LESS": "Read less",
"WROTE": "wrote:",
"FROM": "from",
"EMAIL": "email",
"EMAIL_SUBJECT": "subject"
"FROM": "From",
"EMAIL": "Email",
"EMAIL_SUBJECT": "Subject",
"PRIVATE": "Private note",
"TRANSCRIPT": "Transcript",
"CREATED_AT": "created {time}",
"UPDATED_AT": "updated {time}",
"SORT_BY": {
"RELEVANCE": "Relevance"
},
"DATE_RANGE": {
"LAST_7_DAYS": "Last 7 days",
"LAST_30_DAYS": "Last 30 days",
"LAST_60_DAYS": "Last 60 days",
"LAST_90_DAYS": "Last 90 days",
"CUSTOM_RANGE": "Custom range:",
"CREATED_BETWEEN": "Created between",
"AND": "and",
"APPLY": "Apply",
"BEFORE_DATE": "Before {date}",
"AFTER_DATE": "After {date}",
"TIME_RANGE": "Filter by time",
"CLEAR_FILTER": "Clear filter"
},
"FILTERS": {
"FILTER_MESSAGE": "Filter messages by:",
"FROM": "Sender",
"IN": "Inbox",
"AGENTS": "Agents",
"CONTACTS": "Contacts",
"INBOXES": "Inboxes",
"NO_AGENTS": "No agents found",
"NO_CONTACTS": "Start by searching to see results",
"NO_INBOXES": "No inboxes found"
}
}
}
@@ -6,7 +6,8 @@
"OPTIONS": {
"NAME": "نام",
"DOMAIN": "دامنه",
"CREATED_AT": "ایجاد شده در"
"CREATED_AT": "ایجاد شده در",
"CONTACTS_COUNT": "Contacts count"
}
},
"ORDER": {
@@ -28,6 +28,7 @@
"TYPES": {
"MEDIA": "Media",
"QUICK_REPLY": "Quick Reply",
"CALL_TO_ACTION": "Call to Action",
"TEXT": "متن"
}
},
@@ -275,6 +275,16 @@
"SIDEBAR": {
"CONTACT": "مخاطب",
"COPILOT": "Copilot"
},
"VOICE_WIDGET": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NOT_ANSWERED_YET": "Not answered yet",
"HANDLED_IN_ANOTHER_TAB": "Being handled in another tab",
"REJECT_CALL": "Reject",
"JOIN_CALL": "Join call",
"END_CALL": "End call"
}
},
"EMAIL_TRANSCRIPT": {

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