Merge pull request #129 from shariquerik/translation

feat: Translation
This commit is contained in:
Shariq Ansari 2024-04-15 20:41:55 +05:30 committed by GitHub
commit 0ab14ecf4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
50 changed files with 708 additions and 551 deletions

12
crm/api/__init__.py Normal file
View File

@ -0,0 +1,12 @@
import frappe
from frappe.translate import get_all_translations
@frappe.whitelist(allow_guest=True)
def get_translations():
if frappe.session.user != "Guest":
language = frappe.db.get_value("User", frappe.session.user, "language")
else:
language = frappe.db.get_single_value("System Settings", "language")
return get_all_translations(language)

View File

@ -1,4 +1,5 @@
import frappe
from frappe import _
from frappe.model.document import get_controller
from frappe.model import no_value_fields
from pypika import Criterion
@ -184,6 +185,7 @@ def get_list_data(
for column in columns:
if column.get("key") not in rows:
rows.append(column.get("key"))
column["label"] = _(column.get("label"))
data = frappe.get_list(
doctype,

View File

@ -1,7 +1,7 @@
<template>
<div class="flex items-center justify-between px-10 py-5 text-lg font-medium">
<div class="flex h-7 items-center text-xl font-semibold text-gray-800">
{{ title }}
{{ __(title) }}
</div>
<Button
v-if="title == 'Emails'"
@ -11,7 +11,7 @@
<template #prefix>
<FeatherIcon name="plus" class="h-4 w-4" />
</template>
<span>New Email</span>
<span>{{ __('New Email') }}</span>
</Button>
<Button
v-else-if="title == 'Calls'"
@ -21,41 +21,41 @@
<template #prefix>
<PhoneIcon class="h-4 w-4" />
</template>
<span>Make a Call</span>
<span>{{ __('Make a Call') }}</span>
</Button>
<Button v-else-if="title == 'Notes'" variant="solid" @click="showNote()">
<template #prefix>
<FeatherIcon name="plus" class="h-4 w-4" />
</template>
<span>New Note</span>
<span>{{ __('New Note') }}</span>
</Button>
<Button v-else-if="title == 'Tasks'" variant="solid" @click="showTask()">
<template #prefix>
<FeatherIcon name="plus" class="h-4 w-4" />
</template>
<span>New Task</span>
<span>{{ __('New Task') }}</span>
</Button>
<Dropdown
v-else
:options="[
{
icon: h(EmailIcon, { class: 'h-4 w-4' }),
label: 'New Email',
label: __('New Email'),
onClick: () => ($refs.emailBox.show = true),
},
{
icon: h(PhoneIcon, { class: 'h-4 w-4' }),
label: 'Make a Call',
label: __('Make a Call'),
onClick: () => makeCall(doc.data.mobile_no),
},
{
icon: h(NoteIcon, { class: 'h-4 w-4' }),
label: 'New Note',
label: __('New Note'),
onClick: () => showNote(),
},
{
icon: h(TaskIcon, { class: 'h-4 w-4' }),
label: 'New Task',
label: __('New Task'),
onClick: () => showTask(),
},
]"
@ -66,7 +66,7 @@
<template #prefix>
<FeatherIcon name="plus" class="h-4 w-4" />
</template>
<span>New</span>
<span>{{ __('New') }}</span>
<template #suffix>
<FeatherIcon
:name="open ? 'chevron-up' : 'chevron-down'"
@ -82,7 +82,7 @@
class="flex flex-1 flex-col items-center justify-center gap-3 text-xl font-medium text-gray-500"
>
<LoadingIndicator class="h-6 w-6" />
<span>Loading...</span>
<span>{{ __('Loading...') }}</span>
</div>
<div v-else-if="activities?.length" class="activities flex-1 overflow-y-auto">
<div
@ -101,8 +101,8 @@
<Dropdown
:options="[
{
label: __('Delete'),
icon: 'trash-2',
label: 'Delete',
onClick: () => deleteNote(note.name),
},
]"
@ -135,7 +135,7 @@
</div>
<Tooltip :text="dateFormat(note.modified, dateTooltipFormat)">
<div class="truncate text-sm text-gray-700">
{{ timeAgo(note.modified) }}
{{ __(timeAgo(note.modified)) }}
</div>
</Tooltip>
</div>
@ -188,7 +188,7 @@
:options="taskStatusOptions(updateTaskStatus, task)"
@click.stop
>
<Tooltip text="Change Status">
<Tooltip :text="__('Change Status')">
<Button variant="ghosted" class="hover:bg-gray-300">
<TaskStatusIcon :status="task.status" />
</Button>
@ -197,15 +197,15 @@
<Dropdown
:options="[
{
label: __('Delete'),
icon: 'trash-2',
label: 'Delete',
onClick: () => {
$dialog({
title: 'Delete Task',
message: 'Are you sure you want to delete this task?',
title: __('Delete Task'),
message: __('Are you sure you want to delete this task?'),
actions: [
{
label: 'Delete',
label: __('Delete'),
theme: 'red',
variant: 'solid',
onClick(close) {
@ -257,12 +257,16 @@
>
<div class="flex items-center justify-between">
<div>
{{ call.type == 'Incoming' ? 'Inbound' : 'Outbound' }} Call
{{
call.type == 'Incoming'
? __('Inbound Call')
: __('Outbound Call')
}}
</div>
<div>
<Tooltip :text="dateFormat(call.creation, dateTooltipFormat)">
<div class="text-sm text-gray-600">
{{ timeAgo(call.creation) }}
{{ __(timeAgo(call.creation)) }}
</div>
</Tooltip>
</div>
@ -270,25 +274,28 @@
<div class="flex items-center justify-between">
<div class="flex items-center gap-1">
<DurationIcon class="h-4 w-4 text-gray-600" />
<div class="text-sm text-gray-600">Duration</div>
<div class="text-sm text-gray-600">{{ __('Duration') }}</div>
<div class="text-sm">
{{ call.duration }}
</div>
</div>
<div
v-if="call.recording_url"
class="flex cursor-pointer select-none items-center gap-1"
@click="call.show_recording = !call.show_recording"
>
<PlayIcon class="h-4 w-4 text-gray-600" />
<div class="text-sm text-gray-600">
{{
call.show_recording ? 'Hide Recording' : 'Listen to Call'
call.show_recording
? __('Hide Recording')
: __('Listen to Call')
}}
</div>
</div>
</div>
<div
v-if="call.show_recording"
v-if="call.show_recording && call.recording_url"
class="flex items-center justify-between rounded border"
>
<audio class="audio-control" controls :src="call.recording_url" />
@ -302,7 +309,7 @@
/>
<div class="ml-1 flex flex-col gap-1">
<div class="text-base font-medium">
{{ call.caller.label }}
{{ __(call.caller.label) }}
</div>
<div class="text-xs text-gray-600">
{{ call.from }}
@ -319,7 +326,7 @@
/>
<div class="ml-1 flex flex-col gap-1">
<div class="text-base font-medium">
{{ call.receiver.label }}
{{ __(call.receiver.label) }}
</div>
<div class="text-xs text-gray-600">
{{ call.to }}
@ -378,12 +385,12 @@
:text="dateFormat(activity.creation, dateTooltipFormat)"
>
<div class="text-sm text-gray-600">
{{ timeAgo(activity.creation) }}
{{ __(timeAgo(activity.creation)) }}
</div>
</Tooltip>
</div>
<div class="flex gap-0.5">
<Tooltip text="Reply">
<Tooltip :text="__('Reply')">
<Button
variant="ghost"
class="text-gray-700"
@ -392,7 +399,7 @@
<ReplyIcon class="h-4 w-4" />
</Button>
</Tooltip>
<Tooltip text="Reply All">
<Tooltip :text="__('Reply All')">
<Button
variant="ghost"
class="text-gray-700"
@ -407,14 +414,16 @@
{{ activity.data.subject }}
</div>
<div class="mb-3 text-sm leading-5 text-gray-600">
<span class="mr-1 text-2xs font-bold text-gray-500">TO:</span>
<span class="mr-1 text-2xs font-bold text-gray-500">
{{ __('TO') }}:
</span>
<span>{{ activity.data.recipients }}</span>
<span v-if="activity.data.cc">, </span>
<span
v-if="activity.data.cc"
class="mr-1 text-2xs font-bold text-gray-500"
>
CC:
{{ __('CC') }}:
</span>
<span v-if="activity.data.cc">{{ activity.data.cc }}</span>
<span v-if="activity.data.bcc">, </span>
@ -422,7 +431,7 @@
v-if="activity.data.bcc"
class="mr-1 text-2xs font-bold text-gray-500"
>
BCC:
{{ __('BCC') }}:
</span>
<span v-if="activity.data.bcc">{{ activity.data.bcc }}</span>
</div>
@ -452,15 +461,15 @@
<span class="font-medium text-gray-800">
{{ activity.owner_name }}
</span>
<span>added a</span>
<span>{{ __('added a') }}</span>
<span class="max-w-xs truncate font-medium text-gray-800">
comment
{{ __('comment') }}
</span>
</div>
<div class="ml-auto whitespace-nowrap">
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
<div class="text-gray-600">
{{ timeAgo(activity.creation) }}
{{ __(timeAgo(activity.creation)) }}
</div>
</Tooltip>
</div>
@ -479,12 +488,16 @@
>
<div class="flex items-center justify-between">
<div>
{{ activity.type == 'Incoming' ? 'Inbound' : 'Outbound' }} Call
{{
activity.type == 'Incoming'
? __('Inbound Call')
: __('Outbound Call')
}}
</div>
<div>
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
<div class="text-sm text-gray-600">
{{ timeAgo(activity.creation) }}
{{ __(timeAgo(activity.creation)) }}
</div>
</Tooltip>
</div>
@ -492,25 +505,28 @@
<div class="flex items-center justify-between">
<div class="flex items-center gap-1">
<DurationIcon class="h-4 w-4 text-gray-600" />
<div class="text-sm text-gray-600">Duration</div>
<div class="text-sm text-gray-600">{{ __('Duration') }}</div>
<div class="text-sm">
{{ activity.duration }}
</div>
</div>
<div
v-if="activity.recording_url"
class="flex cursor-pointer select-none items-center gap-1"
@click="activity.show_recording = !activity.show_recording"
>
<PlayIcon class="h-4 w-4 text-gray-600" />
<div class="text-sm text-gray-600">
{{
activity.show_recording ? 'Hide Recording' : 'Listen to Call'
activity.show_recording
? __('Hide Recording')
: __('Listen to Call')
}}
</div>
</div>
</div>
<div
v-if="activity.show_recording"
v-if="activity.show_recording && activity.recording_url"
class="flex items-center justify-between rounded border"
>
<audio
@ -528,7 +544,7 @@
/>
<div class="ml-1 flex flex-col gap-1">
<div class="text-base font-medium">
{{ activity.caller.label }}
{{ __(activity.caller.label) }}
</div>
<div class="text-xs text-gray-600">
{{ activity.from }}
@ -545,7 +561,7 @@
/>
<div class="ml-1 flex flex-col gap-1">
<div class="text-base font-medium">
{{ activity.receiver.label }}
{{ __(activity.receiver.label) }}
</div>
<div class="text-xs text-gray-600">
{{ activity.to }}
@ -557,17 +573,17 @@
<div v-else class="mb-4 flex flex-col gap-5 py-1.5">
<div class="flex items-start justify-stretch gap-2 text-base">
<div class="inline-flex flex-wrap gap-1 text-gray-600">
<span class="font-medium text-gray-800">{{
activity.owner_name
}}</span>
<span v-if="activity.type">{{ activity.type }}</span>
<span class="font-medium text-gray-800">
{{ activity.owner_name }}
</span>
<span v-if="activity.type">{{ __(activity.type) }}</span>
<span
v-if="activity.data.field_label"
class="max-w-xs truncate font-medium text-gray-800"
>
{{ activity.data.field_label }}
{{ __(activity.data.field_label) }}
</span>
<span v-if="activity.value">{{ activity.value }}</span>
<span v-if="activity.value">{{ __(activity.value) }}</span>
<span
v-if="activity.data.old_value"
class="max-w-xs font-medium text-gray-800"
@ -583,7 +599,7 @@
{{ activity.data.old_value }}
</div>
</span>
<span v-if="activity.to">to</span>
<span v-if="activity.to">{{ __('to') }}</span>
<span
v-if="activity.data.value"
class="max-w-xs font-medium text-gray-800"
@ -604,7 +620,7 @@
<div class="ml-auto whitespace-nowrap">
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
<div class="text-gray-600">
{{ timeAgo(activity.creation) }}
{{ __(timeAgo(activity.creation)) }}
</div>
</Tooltip>
</div>
@ -620,7 +636,7 @@
v-if="activity.data.field_label"
class="max-w-xs truncate text-gray-600"
>
{{ activity.data.field_label }}
{{ __(activity.data.field_label) }}
</span>
<FeatherIcon
name="arrow-right"
@ -628,7 +644,9 @@
/>
</div>
<div class="flex flex-wrap items-center gap-1">
<span v-if="activity.type">{{ startCase(activity.type) }}</span>
<span v-if="activity.type">{{
startCase(__(activity.type))
}}</span>
<span
v-if="activity.data.old_value"
class="max-w-xs font-medium text-gray-800"
@ -644,7 +662,7 @@
{{ activity.data.old_value }}
</div>
</span>
<span v-if="activity.to">to</span>
<span v-if="activity.to">{{ __('to') }}</span>
<span
v-if="activity.data.value"
class="max-w-xs font-medium text-gray-800"
@ -666,7 +684,7 @@
<div class="ml-auto whitespace-nowrap">
<Tooltip :text="dateFormat(activity.creation, dateTooltipFormat)">
<div class="text-gray-600">
{{ timeAgo(activity.creation) }}
{{ __(timeAgo(activity.creation)) }}
</div>
</Tooltip>
</div>
@ -674,7 +692,9 @@
<div v-if="activity.other_versions">
<Button
:label="
activity.show_others ? 'Hide all Changes' : 'Show all Changes'
activity.show_others
? __('Hide all Changes')
: __('Show all Changes')
"
variant="outline"
@click="activity.show_others = !activity.show_others"
@ -696,25 +716,25 @@
class="flex flex-1 flex-col items-center justify-center gap-3 text-xl font-medium text-gray-500"
>
<component :is="emptyTextIcon" class="h-10 w-10" />
<span>{{ emptyText }}</span>
<span>{{ __(emptyText) }}</span>
<Button
v-if="title == 'Calls'"
label="Make a Call"
:label="__('Make a Call')"
@click="makeCall(doc.data.mobile_no)"
/>
<Button
v-else-if="title == 'Notes'"
label="Create Note"
:label="__('Create Note')"
@click="showNote()"
/>
<Button
v-else-if="title == 'Emails'"
label="New Email"
:label="__('New Email')"
@click="$refs.emailBox.show = true"
/>
<Button
v-else-if="title == 'Tasks'"
label="Create Task"
:label="__('Create Task')"
@click="showTask()"
/>
</div>
@ -785,11 +805,10 @@ import {
TextEditor,
Avatar,
createResource,
createListResource,
call,
} from 'frappe-ui'
import { useElementVisibility } from '@vueuse/core'
import { ref, computed, h, markRaw, watch, nextTick, onMounted } from 'vue'
import { ref, computed, h, markRaw, watch, nextTick } from 'vue'
import { useRoute } from 'vue-router'
const { makeCall } = globalStore()

View File

@ -1,7 +1,7 @@
<template>
<NestedPopover>
<template #target>
<Button label="Columns">
<Button :label="__('Columns')">
<template #prefix>
<ColumnsIcon class="h-4" />
</template>
@ -24,7 +24,7 @@
>
<div class="flex items-center gap-2">
<DragIcon class="h-3.5" />
<div>{{ element.label }}</div>
<div>{{ __(element.label) }}</div>
</div>
<div class="flex cursor-pointer items-center gap-1">
<Button
@ -56,7 +56,7 @@
class="w-full !justify-start !text-gray-600"
variant="ghost"
@click="togglePopover()"
label="Add Column"
:label="__('Add Column')"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
@ -69,7 +69,7 @@
class="w-full !justify-start !text-gray-600"
variant="ghost"
@click="reset(close)"
label="Reset Changes"
:label="__('Reset Changes')"
>
<template #prefix>
<ReloadIcon class="h-4" />
@ -80,7 +80,7 @@
class="w-full !justify-start !text-gray-600"
variant="ghost"
@click="resetToDefault(close)"
label="Reset to Default"
:label="__('Reset to Default')"
>
<template #prefix>
<ReloadIcon class="h-4" />
@ -96,32 +96,32 @@
<FormControl
type="text"
size="md"
label="Label"
:label="__('Label')"
v-model="column.label"
class="w-full"
placeholder="Column Label"
:placeholder="__('First Name')"
/>
<FormControl
type="text"
size="md"
label="Width"
:label="__('Width')"
class="w-full"
v-model="column.width"
placeholder="Column Width"
description="Width can be in number, pixel or rem (eg. 3, 30px, 10rem)"
placeholder="10rem"
:description="__('Width can be in number, pixel or rem (eg. 3, 30px, 10rem)')"
:debounce="500"
/>
</div>
<div class="flex w-full gap-2 border-t pt-2">
<Button
variant="subtle"
label="Cancel"
:label="__('Cancel')"
class="w-full flex-1"
@click="cancelUpdate"
/>
<Button
variant="solid"
label="Update"
:label="__('Update')"
class="w-full flex-1"
@click="updateColumn(column)"
/>

View File

@ -64,11 +64,11 @@
</FileUploader>
</div>
<div class="mt-2 flex items-center justify-end space-x-2 sm:mt-0">
<Button v-bind="discardButtonProps || {}" label="Discard" />
<Button v-bind="discardButtonProps || {}" :label="__('Discard')" />
<Button
variant="solid"
v-bind="submitButtonProps || {}"
label="Submit"
:label="__('Comment')"
/>
</div>
</div>

View File

@ -5,7 +5,7 @@
ref="sendEmailRef"
variant="ghost"
:class="[showEmailBox ? '!bg-gray-300 hover:!bg-gray-200' : '']"
label="Reply"
:label="__('Reply')"
@click="toggleEmailBox()"
>
<template #prefix>
@ -14,7 +14,7 @@
</Button>
<Button
variant="ghost"
label="Comment"
:label="__('Comment')"
:class="[showCommentBox ? '!bg-gray-300 hover:!bg-gray-200' : '']"
@click="toggleCommentBox()"
>
@ -25,12 +25,12 @@
</div>
<div v-if="showEmailBox" class="flex gap-1.5">
<Button
label="CC"
:label="__('CC')"
@click="toggleCC()"
:class="[newEmailEditor.cc ? 'bg-gray-300 hover:bg-gray-200' : '']"
/>
<Button
label="BCC"
:label="__('BCC')"
@click="toggleBCC()"
:class="[newEmailEditor.bcc ? 'bg-gray-300 hover:bg-gray-200' : '']"
/>
@ -66,7 +66,9 @@
v-model:attachments="attachments"
:doctype="doctype"
:subject="subject"
placeholder="Add a reply..."
:placeholder="
__('Hi John, \n\nCan you please provide more details on this...')
"
/>
</div>
<div v-show="showCommentBox">
@ -88,7 +90,7 @@
v-model="doc.data"
v-model:attachments="attachments"
:doctype="doctype"
placeholder="Add a comment..."
:placeholder="__('@John, can you please check this?')"
/>
</div>
</template>

View File

@ -1,7 +1,7 @@
<template>
<div class="space-y-1.5">
<label class="block" :class="labelClasses" v-if="attrs.label">
{{ attrs.label }}
{{ __(attrs.label) }}
</label>
<Autocomplete
ref="autocomplete"
@ -33,7 +33,7 @@
<Button
variant="ghost"
class="w-full !justify-start"
label="Create New"
:label="__('Create New')"
@click="attrs.onCreate(value, close)"
>
<template #prefix>
@ -45,7 +45,7 @@
<Button
variant="ghost"
class="w-full !justify-start"
label="Clear"
:label="__('Clear')"
@click="() => clearValue(close)"
>
<template #prefix>

View File

@ -10,7 +10,7 @@
>
<template #top>
<div class="mx-10 flex items-center gap-2 border-t py-2.5">
<span class="text-xs text-gray-500">SUBJECT:</span>
<span class="text-xs text-gray-500">{{ __('SUBJECT') }}:</span>
<TextInput
class="flex-1 border-none bg-white hover:bg-white focus:border-none focus:!shadow-none focus-visible:!ring-0"
v-model="subject"
@ -20,12 +20,14 @@
class="mx-10 flex items-center gap-2 border-t py-2.5"
:class="[cc || bcc ? 'border-b' : '']"
>
<span class="text-xs text-gray-500">TO:</span>
<span class="text-xs text-gray-500">{{ __('TO') }}:</span>
<MultiselectInput
class="flex-1"
v-model="toEmails"
:validate="validateEmail"
:error-message="(value) => `${value} is an invalid email address`"
:error-message="
(value) => __('{0} is an invalid email address', [value])
"
/>
</div>
<div
@ -33,23 +35,27 @@
class="mx-10 flex items-center gap-2 py-2.5"
:class="bcc ? 'border-b' : ''"
>
<span class="text-xs text-gray-500">CC:</span>
<span class="text-xs text-gray-500">{{ __('CC') }}:</span>
<MultiselectInput
ref="ccInput"
class="flex-1"
v-model="ccEmails"
:validate="validateEmail"
:error-message="(value) => `${value} is an invalid email address`"
:error-message="
(value) => __('{0} is an invalid email address', [value])
"
/>
</div>
<div v-if="bcc" class="mx-10 flex items-center gap-2 py-2.5">
<span class="text-xs text-gray-500">BCC:</span>
<span class="text-xs text-gray-500">{{ __('BCC') }}:</span>
<MultiselectInput
ref="bccInput"
class="flex-1"
v-model="bccEmails"
:validate="validateEmail"
:error-message="(value) => `${value} is an invalid email address`"
:error-message="
(value) => __('{0} is an invalid email address', [value])
"
/>
</div>
</template>
@ -114,11 +120,11 @@
</div>
</div>
<div class="mt-2 flex items-center justify-end space-x-2 sm:mt-0">
<Button v-bind="discardButtonProps || {}" label="Discard" />
<Button v-bind="discardButtonProps || {}" :label="__('Discard')" />
<Button
variant="solid"
v-bind="submitButtonProps || {}"
label="Submit"
:label="__('Send')"
/>
</div>
</div>
@ -158,7 +164,7 @@ const props = defineProps({
},
subject: {
type: String,
default: 'Email from Lead',
default: __('Email from Lead'),
},
editorProps: {
type: Object,

View File

@ -1,7 +1,7 @@
<template>
<NestedPopover>
<template #target>
<Button label="Filter">
<Button :label="__('Filter')">
<template #prefix><FilterIcon class="h-4" /></template>
<template v-if="filters?.size" #suffix>
<div
@ -24,14 +24,14 @@
>
<div class="flex items-center gap-2">
<div class="w-13 pl-2 text-end text-base text-gray-600">
{{ i == 0 ? 'Where' : 'And' }}
{{ i == 0 ? __('Where') : __('And') }}
</div>
<div id="fieldname" class="!min-w-[140px]">
<Autocomplete
:value="f.field.fieldname"
:options="filterableFields.data"
@change="(e) => updateFilter(e, i)"
placeholder="Filter by..."
:placeholder="__('First Name')"
/>
</div>
<div id="operator">
@ -40,7 +40,7 @@
v-model="f.operator"
@change="(e) => updateOperator(e, f)"
:options="getOperators(f.field.fieldtype, f.field.fieldname)"
placeholder="Operator"
placeholder="Equals"
/>
</div>
<div id="value" class="!min-w-[140px]">
@ -48,7 +48,7 @@
:is="getValSelect(f)"
v-model="f.value"
@change="(v) => updateValue(v, f)"
placeholder="Value"
placeholder="John Doe"
/>
</div>
</div>
@ -58,21 +58,21 @@
v-else
class="mb-3 flex h-7 items-center px-3 text-sm text-gray-600"
>
Empty - Choose a field to filter by
{{ __('Empty - Choose a field to filter by') }}
</div>
<div class="flex items-center justify-between gap-2">
<Autocomplete
value=""
:options="filterableFields.data"
@change="(e) => setfilter(e)"
placeholder="Filter by..."
:placeholder="__('First name')"
>
<template #target="{ togglePopover }">
<Button
class="!text-gray-600"
variant="ghost"
@click="togglePopover()"
label="Add Filter"
:label="__('Add Filter')"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
@ -84,7 +84,7 @@
v-if="filters?.size"
class="!text-gray-600"
variant="ghost"
label="Clear all Filter"
:label="__('Clear all Filter')"
@click="clearfilter(close)"
/>
</div>
@ -529,71 +529,71 @@ const oppositeOperatorMap = {
const timespanOptions = [
{
label: 'Last Week',
label: __('Last Week'),
value: 'last week',
},
{
label: 'Last Month',
label: __('Last Month'),
value: 'last month',
},
{
label: 'Last Quarter',
label: __('Last Quarter'),
value: 'last quarter',
},
{
label: 'Last 6 Months',
label: __('Last 6 Months'),
value: 'last 6 months',
},
{
label: 'Last Year',
label: __('Last Year'),
value: 'last year',
},
{
label: 'Yesterday',
label: __('Yesterday'),
value: 'yesterday',
},
{
label: 'Today',
label: __('Today'),
value: 'today',
},
{
label: 'Tomorrow',
label: __('Tomorrow'),
value: 'tomorrow',
},
{
label: 'This Week',
label: __('This Week'),
value: 'this week',
},
{
label: 'This Month',
label: __('This Month'),
value: 'this month',
},
{
label: 'This Quarter',
label: __('This Quarter'),
value: 'this quarter',
},
{
label: 'This Year',
label: __('This Year'),
value: 'this year',
},
{
label: 'Next Week',
label: __('Next Week'),
value: 'next week',
},
{
label: 'Next Month',
label: __('Next Month'),
value: 'next month',
},
{
label: 'Next Quarter',
label: __('Next Quarter'),
value: 'next quarter',
},
{
label: 'Next 6 Months',
label: __('Next 6 Months'),
value: 'next 6 months',
},
{
label: 'Next Year',
label: __('Next Year'),
value: 'next year',
},
]

View File

@ -8,24 +8,11 @@
>
<path
d="M93.7101 0H23.8141C10.7986 0 0.247559 10.5511 0.247559 23.5665V93.4626C0.247559 106.478 10.7986 117.029 23.8141 117.029H93.7101C106.726 117.029 117.277 106.478 117.277 93.4626V23.5665C117.277 10.5511 106.726 0 93.7101 0Z"
fill="url(#paint0_angular_0_12)"
fill="#DB4EE0"
/>
<path
d="M21.2487 27.5115V38.0121H85.7749V45.8876L62.5161 68.4638V80.7495L54.9556 83.3222V68.4638C54.9556 68.4638 41.1474 55.0756 36.3171 50.3503H21.3013L42.6174 71.089C43.825 72.2441 44.5076 73.8716 44.5076 75.5517V87.9424L73.0167 88.0474V75.5517C73.0167 73.8716 73.6992 72.2441 74.9068 71.089L96.2755 50.2978V27.5115H21.2487Z"
d="M21.2488 27.5116V38.0122H85.7749V45.8876L62.5161 68.4639V80.7496L54.9557 83.3222V68.4639C54.9557 68.4639 41.1474 55.0756 36.3171 50.3504H21.3013L42.6175 71.089C43.825 72.2441 44.5076 73.8717 44.5076 75.5518V87.9425L73.0167 88.0475V75.5518C73.0167 73.8717 73.6992 72.2441 74.9068 71.089L96.2755 50.2979V27.5116H21.2488Z"
fill="#F1FCFF"
/>
<defs>
<radialGradient
id="paint0_angular_0_12"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(81.5001 -21.5) rotate(67.4893) scale(262.5 101.567)"
>
<stop offset="0.494792" stop-color="#E953E6" />
<stop offset="1" stop-color="#BA27D1" />
</radialGradient>
</defs>
</svg>
</template>

View File

@ -10,7 +10,7 @@
<div class="mb-3 flex flex-col">
<SidebarLink
id="notifications-btn"
label="Notifications"
:label="__('Notifications')"
:icon="NotificationsIcon"
:isCollapsed="isSidebarCollapsed"
@click="() => toggleNotificationPanel()"
@ -45,7 +45,7 @@
<template #header="{ opened, hide, toggle }">
<div
v-if="!hide"
class="flex cursor-pointer gap-1.5 px-1 text-sm font-medium text-gray-600 transition-all duration-300 ease-in-out"
class="flex cursor-pointer gap-1.5 px-1 text-base font-medium text-gray-600 transition-all duration-300 ease-in-out"
:class="
isSidebarCollapsed
? 'ml-0 h-0 overflow-hidden opacity-0'
@ -59,7 +59,7 @@
:class="{ 'rotate-90': opened }"
/>
<span class="uppercase">
{{ view.name }}
{{ __(view.name) }}
</span>
</div>
</template>
@ -67,7 +67,7 @@
<SidebarLink
v-for="link in view.views"
:icon="link.icon"
:label="link.label"
:label="__(link.label)"
:to="link.to"
:isCollapsed="isSidebarCollapsed"
class="mx-2 my-0.5"
@ -78,19 +78,19 @@
</div>
<div class="m-2 flex flex-col gap-1">
<SidebarLink
label="Docs"
:label="__('Docs')"
:isCollapsed="isSidebarCollapsed"
icon="book-open"
@click="() => openDocs()"
/>
<SidebarLink
:label="isSidebarCollapsed ? 'Expand' : 'Collapse'"
:label="isSidebarCollapsed ? __('Expand') : __('Collapse')"
:isCollapsed="isSidebarCollapsed"
@click="isSidebarCollapsed = !isSidebarCollapsed"
class=""
>
<template #icon>
<span class="grid h-5 w-6 flex-shrink-0 place-items-center">
<span class="grid h-4.5 w-4.5 flex-shrink-0 place-items-center">
<CollapseSidebar
class="h-4.5 w-4.5 text-gray-700 duration-300 ease-in-out"
:class="{ '[transform:rotateY(180deg)]': isSidebarCollapsed }"

View File

@ -53,7 +53,7 @@
:variant="'subtle'"
:theme="item.color"
size="md"
:label="item.label"
:label="__(item.label)"
/>
</div>
<div v-else-if="column.type === 'Check'">

View File

@ -172,14 +172,14 @@ function editValues(selections, unselectAll) {
function deleteValues(selections, unselectAll) {
$dialog({
title: 'Delete',
message: `Are you sure you want to delete ${selections.size} item${
selections.size > 1 ? 's' : ''
}?`,
title: __('Delete'),
message: __('Are you sure you want to delete {0} item(s)?', [
selections.size,
]),
variant: 'danger',
actions: [
{
label: 'Delete',
label: __('Delete'),
variant: 'solid',
theme: 'red',
onClick: (close) => {
@ -188,7 +188,7 @@ function deleteValues(selections, unselectAll) {
doctype: 'Contact',
}).then(() => {
createToast({
title: 'Deleted successfully',
title: __('Deleted successfully'),
icon: 'check',
iconClasses: 'text-green-600',
})
@ -207,11 +207,11 @@ const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = [
{
label: 'Edit',
label: __('Edit'),
onClick: () => editValues(selections, unselectAll),
},
{
label: 'Delete',
label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll),
},
]

View File

@ -203,14 +203,12 @@ function editValues(selections, unselectAll) {
function deleteValues(selections, unselectAll) {
$dialog({
title: 'Delete',
message: `Are you sure you want to delete ${selections.size} item${
selections.size > 1 ? 's' : ''
}?`,
title: __('Delete'),
message: __('Are you sure you want to delete {0} item(s)?', [selections.size]),
variant: 'danger',
actions: [
{
label: 'Delete',
label: __('Delete'),
variant: 'solid',
theme: 'red',
onClick: (close) => {
@ -219,7 +217,7 @@ function deleteValues(selections, unselectAll) {
doctype: 'CRM Deal',
}).then(() => {
createToast({
title: 'Deleted successfully',
title: __('Deleted successfully'),
icon: 'check',
iconClasses: 'text-green-600',
})
@ -239,17 +237,17 @@ const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = [
{
label: 'Edit',
label: __('Edit'),
onClick: () => editValues(selections, unselectAll),
},
{
label: 'Delete',
label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll),
},
]
customBulkActions.value.forEach((action) => {
actions.push({
label: action.label,
label: __(action.label),
onClick: () =>
action.onClick({
list: list.value,

View File

@ -154,14 +154,14 @@ function editValues(selections, unselectAll) {
function deleteValues(selections, unselectAll) {
$dialog({
title: 'Delete',
message: `Are you sure you want to delete ${selections.size} item${
selections.size > 1 ? 's' : ''
}?`,
title: __('Delete'),
message: __('Are you sure you want to delete {0} item(s)?', [
selections.size,
]),
variant: 'danger',
actions: [
{
label: 'Delete',
label: __('Delete'),
variant: 'solid',
theme: 'red',
onClick: (close) => {
@ -170,7 +170,7 @@ function deleteValues(selections, unselectAll) {
doctype: 'Email Template',
}).then(() => {
createToast({
title: 'Deleted successfully',
title: __('Deleted successfully'),
icon: 'check',
iconClasses: 'text-green-600',
})
@ -189,11 +189,11 @@ const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = [
{
label: 'Edit',
label: __('Edit'),
onClick: () => editValues(selections, unselectAll),
},
{
label: 'Delete',
label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll),
},
]

View File

@ -212,14 +212,12 @@ function editValues(selections, unselectAll) {
function deleteValues(selections, unselectAll) {
$dialog({
title: 'Delete',
message: `Are you sure you want to delete ${selections.size} item${
selections.size > 1 ? 's' : ''
}?`,
title: __('Delete'),
message: __('Are you sure you want to delete {0} item(s)?', [selections.size]),
variant: 'danger',
actions: [
{
label: 'Delete',
label: __('Delete'),
variant: 'solid',
theme: 'red',
onClick: (close) => {
@ -228,7 +226,7 @@ function deleteValues(selections, unselectAll) {
doctype: 'CRM Lead',
}).then(() => {
createToast({
title: 'Deleted successfully',
title: __('Deleted successfully'),
icon: 'check',
iconClasses: 'text-green-600',
})
@ -248,17 +246,17 @@ const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = [
{
label: 'Edit',
label: __('Edit'),
onClick: () => editValues(selections, unselectAll),
},
{
label: 'Delete',
label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll),
},
]
customBulkActions.value.forEach((action) => {
actions.push({
label: action.label,
label: __(action.label),
onClick: () =>
action.onClick({
list: list.value,

View File

@ -157,14 +157,14 @@ function editValues(selections, unselectAll) {
function deleteValues(selections, unselectAll) {
$dialog({
title: 'Delete',
message: `Are you sure you want to delete ${selections.size} item${
selections.size > 1 ? 's' : ''
}?`,
title: __('Delete'),
message: __('Are you sure you want to delete {0} item(s)?', [
selections.size,
]),
variant: 'danger',
actions: [
{
label: 'Delete',
label: __('Delete'),
variant: 'solid',
theme: 'red',
onClick: (close) => {
@ -173,7 +173,7 @@ function deleteValues(selections, unselectAll) {
doctype: 'CRM Organization',
}).then(() => {
createToast({
title: 'Deleted successfully',
title: __('Deleted successfully'),
icon: 'check',
iconClasses: 'text-green-600',
})
@ -192,11 +192,11 @@ const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = [
{
label: 'Edit',
label: __('Edit'),
onClick: () => editValues(selections, unselectAll),
},
{
label: 'Delete',
label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll),
},
]

View File

@ -176,14 +176,12 @@ function editValues(selections, unselectAll) {
function deleteValues(selections, unselectAll) {
$dialog({
title: 'Delete',
message: `Are you sure you want to delete ${selections.size} item${
selections.size > 1 ? 's' : ''
}?`,
title: __('Delete'),
message: __('Are you sure you want to delete {0} item(s)?'),
variant: 'danger',
actions: [
{
label: 'Delete',
label: __('Delete'),
variant: 'solid',
theme: 'red',
onClick: (close) => {
@ -192,7 +190,7 @@ function deleteValues(selections, unselectAll) {
doctype: 'CRM Task',
}).then(() => {
createToast({
title: 'Deleted successfully',
title: __('Deleted successfully'),
icon: 'check',
iconClasses: 'text-green-600',
})
@ -211,11 +209,11 @@ const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = [
{
label: 'Edit',
label: __('Edit'),
onClick: () => editValues(selections, unselectAll),
},
{
label: 'Delete',
label: __('Delete'),
onClick: () => deleteValues(selections, unselectAll),
},
]

View File

@ -2,11 +2,11 @@
<Dialog
v-model="show"
:options="{
title: 'Assign To',
title: __('Assign To'),
size: 'xl',
actions: [
{
label: 'Cancel',
label: __('Cancel'),
variant: 'subtle',
onClick: () => {
assignees = oldAssignees
@ -14,7 +14,7 @@
},
},
{
label: 'Update',
label: __('Update'),
variant: 'solid',
onClick: () => updateAssignees(),
},
@ -65,7 +65,7 @@
</Button>
</Tooltip>
</div>
<ErrorMessage class="mt-2" v-if="error" :message="error" />
<ErrorMessage class="mt-2" v-if="error" :message="__(error)" />
</template>
</Dialog>
</template>

View File

@ -5,7 +5,7 @@
<div class="mb-5 flex items-center justify-between">
<div>
<h3 class="text-2xl font-semibold leading-6 text-gray-900">
{{ dialogOptions.title || 'Untitled' }}
{{ __(dialogOptions.title) || __('Untitled') }}
</h3>
</div>
<div class="flex items-center gap-1">
@ -64,14 +64,14 @@
v-if="field.type === 'link'"
variant="outline"
size="md"
:label="field.label"
:label="__(field.label)"
v-model="_contact[field.name]"
:doctype="field.doctype"
:placeholder="field.placeholder"
/>
<div class="space-y-1.5" v-if="field.type === 'dropdown'">
<label class="block text-base text-gray-600">
{{ field.label }}
{{ __(field.label) }}
</label>
<NestedPopover>
<template #target="{ open }">
@ -101,7 +101,7 @@
/>
<div v-else>
<div class="p-1.5 px-7 text-base text-gray-500">
No {{ field.label }} Available
{{ __('No {0} Available', [field.label]) }}
</div>
</div>
</div>
@ -109,7 +109,7 @@
<Button
variant="ghost"
class="w-full !justify-start"
label="Create New"
:label="__('Create New')"
@click="field.create()"
>
<template #prefix>
@ -126,7 +126,7 @@
variant="outline"
size="md"
type="text"
:label="field.label"
:label="__(field.label)"
:placeholder="field.placeholder"
v-model="_contact[field.name]"
/>
@ -143,7 +143,7 @@
:key="action.label"
v-bind="action"
>
{{ action.label }}
{{ __(action.label) }}
</Button>
</div>
</div>

View File

@ -1,23 +1,23 @@
<template>
<Dialog v-model="show" :options="{ title: 'Bulk Edit' }">
<Dialog v-model="show" :options="{ title: __('Bulk Edit') }">
<template #body-content>
<div class="mb-4">
<div class="mb-1.5 text-sm text-gray-600">Field</div>
<div class="mb-1.5 text-sm text-gray-600">{{ __('Field') }}</div>
<Autocomplete
:value="field.label"
:options="fields.data"
@change="(e) => changeField(e)"
placeholder="Select Field..."
:placeholder="__('Source')"
/>
</div>
<div>
<div class="mb-1.5 text-sm text-gray-600">Value</div>
<div class="mb-1.5 text-sm text-gray-600">{{ __('Value') }}</div>
<component
:is="getValueComponent(field)"
:value="newValue"
size="md"
@change="(v) => updateValue(v)"
placeholder="Value"
:placeholder="__('Contact Us')"
/>
</div>
</template>
@ -27,7 +27,7 @@
variant="solid"
@click="updateValues"
:loading="loading"
:label="`Update ${recordCount} Records`"
:label="__('Update {0} Records', [recordCount])"
/>
</template>
</Dialog>

View File

@ -2,11 +2,11 @@
<Dialog
v-model="show"
:options="{
title: editMode ? emailTemplate.name : 'Create Email Template',
title: editMode ? __(emailTemplate.name) : __('Create Email Template'),
size: 'xl',
actions: [
{
label: editMode ? 'Update' : 'Create',
label: editMode ? __('Update') : __('Create'),
variant: 'solid',
onClick: () => (editMode ? updateEmailTemplate() : callInsertDoc()),
},
@ -18,41 +18,41 @@
<div class="flex gap-4">
<div class="flex-1">
<div class="mb-1.5 text-sm text-gray-600">
Name
{{ __('Name') }}
<span class="text-red-500">*</span>
</div>
<TextInput
ref="nameRef"
variant="outline"
v-model="_emailTemplate.name"
placeholder="Add name"
:placeholder="__('Payment Reminder')"
/>
</div>
<div class="flex-1">
<div class="mb-1.5 text-sm text-gray-600">Doctype</div>
<div class="mb-1.5 text-sm text-gray-600">{{ __('Doctype') }}</div>
<Select
variant="outline"
v-model="_emailTemplate.reference_doctype"
:options="['CRM Deal', 'CRM Lead']"
placeholder="Select Doctype"
:placeholder="__('CRM Deal')"
/>
</div>
</div>
<div>
<div class="mb-1.5 text-sm text-gray-600">
Subject
{{ __('Subject') }}
<span class="text-red-500">*</span>
</div>
<TextInput
ref="subjectRef"
variant="outline"
v-model="_emailTemplate.subject"
placeholder="Add subject"
:placeholder="__('Payment Reminder from Frappé - (#{{ name }})')"
/>
</div>
<div>
<div class="mb-1.5 text-sm text-gray-600">
Content
{{ __('Content') }}
<span class="text-red-500">*</span>
</div>
<TextEditor
@ -62,13 +62,13 @@
:bubbleMenu="true"
:content="_emailTemplate.response"
@change="(val) => (_emailTemplate.response = val)"
placeholder="Type a Content"
:placeholder="__('Dear {{ lead_name }}, \n\nThis is a reminder for the payment of {{ grand_total }}. \n\nThanks, \nFrappé')"
/>
</div>
<div>
<Checkbox v-model="_emailTemplate.enabled" label="Enabled" />
<Checkbox v-model="_emailTemplate.enabled" :label="__('Enabled')" />
</div>
<ErrorMessage :message="errorMessage" />
<ErrorMessage :message="__(errorMessage)" />
</div>
</template>
</Dialog>

View File

@ -1,16 +1,22 @@
<template>
<Dialog v-model="show" :options="{ title: 'Email Templates', size: '4xl' }">
<Dialog
v-model="show"
:options="{ title: __('Email Templates'), size: '4xl' }"
>
<template #body-content>
<TextInput
ref="searchInput"
v-model="search"
type="text"
class="mb-2 w-full"
placeholder="Search"
/>
:placeholder="__('Payment Reminder')"
>
<template #prefix>
<FeatherIcon name="search" class="h-4 w-4 text-gray-500" />
</template>
</TextInput>
<div
v-if="filteredTemplates.length"
class="grid max-h-[560px] grid-cols-3 gap-2 overflow-y-auto"
class="mt-2 grid max-h-[560px] grid-cols-3 gap-2 overflow-y-auto"
>
<div
v-for="template in filteredTemplates"
@ -22,7 +28,7 @@
{{ template.name }}
</div>
<div v-if="template.subject" class="text-sm text-gray-600">
Subject: {{ template.subject }}
{{ __('Subject: {0}', [template.subject]) }}
</div>
<TextEditor
v-if="template.response"
@ -33,11 +39,13 @@
/>
</div>
</div>
<div v-else>
<div v-else class="mt-2">
<div class="flex h-56 flex-col items-center justify-center">
<div class="text-lg text-gray-500">No templates found</div>
<div class="text-lg text-gray-500">
{{ __('No templates found') }}
</div>
<Button
label="Create New"
:label="__('Create New')"
class="mt-4"
@click="
() => {

View File

@ -5,7 +5,7 @@
size: 'xl',
actions: [
{
label: editMode ? 'Update' : 'Create',
label: editMode ? __('Update') : __('Create'),
variant: 'solid',
onClick: () => updateNote(),
},
@ -15,14 +15,16 @@
<template #body-title>
<div class="flex items-center gap-3">
<h3 class="text-2xl font-semibold leading-6 text-gray-900">
{{ editMode ? 'Edit Note' : 'Create Note' }}
{{ editMode ? __('Edit Note') : __('Create Note') }}
</h3>
<Button
v-if="_note?.reference_docname"
variant="outline"
size="sm"
:label="
_note.reference_doctype == 'CRM Deal' ? 'Open Deal' : 'Open Lead'
_note.reference_doctype == 'CRM Deal'
? __('Open Deal')
: __('Open Lead')
"
@click="redirect()"
>
@ -35,16 +37,16 @@
<template #body-content>
<div class="flex flex-col gap-4">
<div>
<div class="mb-1.5 text-sm text-gray-600">Title</div>
<div class="mb-1.5 text-sm text-gray-600">{{ __('Title') }}</div>
<TextInput
ref="title"
variant="outline"
v-model="_note.title"
placeholder="Add title"
:placeholder="__('Call with John Doe')"
/>
</div>
<div>
<div class="mb-1.5 text-sm text-gray-600">Content</div>
<div class="mb-1.5 text-sm text-gray-600">{{ __('Content') }}</div>
<TextEditor
variant="outline"
ref="content"
@ -52,7 +54,9 @@
:bubbleMenu="true"
:content="_note.content"
@change="(val) => (_note.content = val)"
placeholder="Type a Content"
:placeholder="
__('Took a call with John Doe and discussed the new project.')
"
/>
</div>
</div>
@ -64,7 +68,7 @@
import ArrowUpRightIcon from '@/components/Icons/ArrowUpRightIcon.vue'
import { TextEditor, call } from 'frappe-ui'
import { ref, nextTick, watch } from 'vue'
import { useRouter } from 'vue-router';
import { useRouter } from 'vue-router'
const props = defineProps({
note: {

View File

@ -5,7 +5,7 @@
<div class="mb-5 flex items-center justify-between">
<div>
<h3 class="text-2xl font-semibold leading-6 text-gray-900">
{{ dialogOptions.title || 'Untitled' }}
{{ __(dialogOptions.title) || __('Untitled') }}
</h3>
</div>
<div class="flex items-center gap-1">
@ -41,67 +41,68 @@
type="text"
ref="title"
size="md"
label="Organization Name"
:label="__('Organization Name')"
variant="outline"
v-model="_organization.organization_name"
placeholder="Add Organization Name"
placeholder="Frappé Technologies"
/>
<div class="flex gap-4">
<FormControl
class="flex-1"
type="text"
size="md"
label="Website"
:label="__('Website')"
variant="outline"
v-model="_organization.website"
placeholder="Add Website"
placeholder="https://example.com"
/>
<FormControl
class="flex-1"
type="text"
size="md"
label="Annual Revenue"
:label="__('Annual Revenue')"
variant="outline"
v-model="_organization.annual_revenue"
placeholder="Add Annual Revenue"
:placeholder="__('9,999,999')"
/>
</div>
<Link
class="flex-1"
size="md"
label="Territory"
:label="__('Territory')"
variant="outline"
v-model="_organization.territory"
doctype="CRM Territory"
placeholder="Add Territory"
placeholder="India"
/>
<div class="flex gap-4">
<FormControl
class="flex-1"
type="select"
:options="[
'1-10',
'11-50',
'51-200',
'201-500',
'501-1000',
'1001-5000',
'5001-10000',
'10001+',
{ label: __('1-10'), value: '1-10' },
{ label: __('11-50'), value: '11-50' },
{ label: __('51-200'), value: '51-200' },
{ label: __('201-500'), value: '201-500' },
{ label: __('501-1000'), value: '501-1000' },
{ label: __('1001-5000'), value: '1001-5000' },
{ label: __('5001-10000'), value: '5001-10000' },
{ label: __('10001+'), value: '10001+' },
]"
size="md"
label="No. of Employees"
:label="__('No of Employees')"
variant="outline"
:placeholder="__('1-10')"
v-model="_organization.no_of_employees"
/>
<Link
class="flex-1"
size="md"
label="Industry"
:label="__('Industry')"
variant="outline"
v-model="_organization.industry"
doctype="CRM Industry"
placeholder="Add Industry"
:placeholder="__('Technology')"
/>
</div>
</div>
@ -115,7 +116,7 @@
v-for="action in dialogOptions.actions"
:key="action.label"
v-bind="action"
:label="action.label"
:label="__(action.label)"
:loading="loading"
/>
</div>
@ -236,14 +237,14 @@ function handleOrganizationUpdate(doc, renamed = false) {
const dialogOptions = computed(() => {
let title = !editMode.value
? 'New Organization'
: _organization.value.organization_name
? __('New Organization')
: __(_organization.value.organization_name)
let size = detailMode.value ? '' : 'xl'
let actions = detailMode.value
? []
: [
{
label: editMode.value ? 'Save' : 'Create',
label: editMode.value ? __('Save') : __('Create'),
variant: 'solid',
onClick: () =>
editMode.value ? updateOrganization() : callInsertDoc(),

View File

@ -5,7 +5,7 @@
size: 'xl',
actions: [
{
label: editMode ? 'Update' : 'Create',
label: editMode ? __('Update') : __('Create'),
variant: 'solid',
onClick: () => updateTask(),
},
@ -15,14 +15,14 @@
<template #body-title>
<div class="flex items-center gap-3">
<h3 class="text-2xl font-semibold leading-6 text-gray-900">
{{ editMode ? 'Edit Task' : 'Create Task' }}
{{ editMode ? __('Edit Task') : __('Create Task') }}
</h3>
<Button
v-if="task?.reference_docname"
variant="outline"
size="sm"
:label="
task.reference_doctype == 'CRM Deal' ? 'Open Deal' : 'Open Lead'
task.reference_doctype == 'CRM Deal' ? __('Open Deal') : __('Open Lead')
"
@click="redirect()"
>
@ -35,16 +35,16 @@
<template #body-content>
<div class="flex flex-col gap-4">
<div>
<div class="mb-1.5 text-sm text-gray-600">Title</div>
<div class="mb-1.5 text-sm text-gray-600">{{ __('Title') }}</div>
<TextInput
ref="title"
variant="outline"
v-model="_task.title"
placeholder="Add Title"
:placeholder="__('Call with John Doe')"
/>
</div>
<div>
<div class="mb-1.5 text-sm text-gray-600">Description</div>
<div class="mb-1.5 text-sm text-gray-600">{{ __('Description') }}</div>
<TextEditor
variant="outline"
ref="description"
@ -52,7 +52,7 @@
:bubbleMenu="true"
:content="_task.description"
@change="(val) => (_task.description = val)"
placeholder="Type a Description"
:placeholder="__('Took a call with John Doe and discussed the new project.')"
/>
</div>
<div class="flex items-center gap-2">
@ -68,7 +68,7 @@
:value="getUser(_task.assigned_to).full_name"
doctype="User"
@change="(option) => (_task.assigned_to = option)"
placeholder="Assignee"
:placeholder="__('John Doe')"
:hideMe="true"
>
<template #prefix>
@ -90,7 +90,7 @@
icon-left="calendar"
:value="_task.due_date"
@change="(val) => (_task.due_date = val)"
placeholder="Due Date"
:placeholder="__('01/04/2024 11:30 PM')"
input-class="border-none"
/>
<Dropdown :options="taskPriorityOptions(updateTaskPriority)">

View File

@ -3,17 +3,17 @@
v-model="show"
:options="{
title: editMode
? 'Edit View'
? __('Edit View')
: duplicateMode
? 'Duplicate View'
: 'Create View',
? __('Duplicate View')
: __('Create View'),
actions: [
{
label: editMode
? 'Save Changes'
? __('Save Changes')
: duplicateMode
? 'Duplicate'
: 'Create',
? __('Duplicate')
: __('Create'),
variant: 'solid',
onClick: () => (editMode ? update() : create()),
},
@ -25,8 +25,8 @@
variant="outline"
size="md"
type="text"
label="View Name"
placeholder="View Name"
:label="__('View Name')"
:placeholder="__('My Open Deals')"
v-model="view.label"
/>
</template>
@ -51,7 +51,6 @@ const props = defineProps({
},
})
const show = defineModel()
const view = defineModel('view')

View File

@ -14,9 +14,9 @@
<div
class="z-20 flex items-center justify-between border-b bg-white px-5 py-2.5"
>
<div class="text-base font-medium">Notifications</div>
<div class="text-base font-medium">{{ __('Notifications') }}</div>
<div class="flex gap-1">
<Tooltip text="Mark all as read">
<Tooltip :text="__('Mark all as read')">
<div>
<Button
variant="ghost"
@ -28,7 +28,7 @@
</Button>
</div>
</Tooltip>
<Tooltip text="Close">
<Tooltip :text="__('Close')">
<div>
<Button variant="ghost" @click="() => toggleNotificationPanel()">
<template #icon>
@ -58,17 +58,17 @@
<UserAvatar :user="n.from_user.name" size="lg" />
</div>
<div>
<div class="mb-2 space-x-1 leading-5 text-gray-700">
<div class="mb-2 space-x-1 leading-5 text-gray-600">
<span class="font-medium text-gray-900">
{{ n.from_user.full_name }}
</span>
<span>mentioned you in {{ n.reference_doctype }}</span>
<span>{{ __('mentioned you in {0}', [n.reference_doctype]) }}</span>
<span class="font-medium text-gray-900">
{{ n.reference_name }}
</span>
</div>
<div class="text-sm text-gray-600">
{{ timeAgo(n.creation) }}
{{ __(timeAgo(n.creation)) }}
</div>
</div>
</RouterLink>
@ -79,7 +79,7 @@
>
<NotificationsIcon class="h-20 w-20 text-gray-300" />
<div class="text-lg font-medium text-gray-500">
No new notifications
{{ __('No new notifications') }}
</div>
</div>
</div>
@ -90,7 +90,6 @@ import MarkAsDoneIcon from '@/components/Icons/MarkAsDoneIcon.vue'
import NotificationsIcon from '@/components/Icons/NotificationsIcon.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import { notificationsStore } from '@/stores/notifications'
import { globalStore } from '@/stores/global'
import { timeAgo } from '@/utils'
import { onClickOutside } from '@vueuse/core'
import { Tooltip } from 'frappe-ui'

View File

@ -5,9 +5,9 @@
:key="s.label"
class="flex items-center gap-2 text-base leading-5"
>
<div class="w-[106px] text-sm text-gray-600">{{ s.label }}</div>
<div class="w-[106px] text-sm text-gray-600">{{ __(s.label) }}</div>
<div class="grid min-h-[28px] items-center">
<Tooltip v-if="s.tooltipText" :text="s.tooltipText">
<Tooltip v-if="s.tooltipText" :text="__(s.tooltipText)">
<div class="ml-2 cursor-pointer">
<Badge
v-if="s.type == 'Badge'"
@ -69,12 +69,12 @@ let slaSection = computed(() => {
tooltipText = dateFormat(data.value.response_by, dateTooltipFormat)
if (new Date(data.value.response_by) < new Date()) {
color = 'red'
if (status == 'In less than a minute') {
if (status == __('In less than a minute')) {
status = 'less than a minute ago'
}
}
} else if (['Fulfilled', 'Failed'].includes(status)) {
status = status + ' in ' + formatTime(data.value.first_response_time)
status = __(status) + ' in ' + formatTime(data.value.first_response_time)
tooltipText = dateFormat(data.value.first_responded_on, dateTooltipFormat)
}
@ -83,7 +83,7 @@ let slaSection = computed(() => {
{
label: 'First Response',
type: 'Badge',
value: status,
value: __(status),
tooltipText: tooltipText,
color: color,
},

View File

@ -10,7 +10,7 @@
class="h-4 text-gray-900 transition-all duration-300 ease-in-out"
:class="{ 'rotate-90': opened }"
/>
{{ label || 'Untitled' }}
{{ __(label) || __('Untitled') }}
</div>
<slot name="actions"></slot>
</div>

View File

@ -7,7 +7,7 @@
class="flex items-center gap-2 px-3 leading-5 first:mt-3"
>
<div class="w-[106px] shrink-0 text-sm text-gray-600">
{{ field.label }}
{{ __(field.label) }}
<span class="text-red-500">{{ field.reqd ? ' *' : '' }}</span>
</div>
<div
@ -17,7 +17,7 @@
v-if="field.read_only && field.type !== 'checkbox'"
class="flex h-7 cursor-pointer items-center px-2 py-1 text-gray-600"
>
<Tooltip :text="field.tooltip">
<Tooltip :text="__(field.tooltip)">
<div>{{ data[field.name] }}</div>
</Tooltip>
</div>

View File

@ -1,28 +1,28 @@
<template>
<button
class="flex h-7 cursor-pointer items-center rounded text-gray-800 duration-300 ease-in-out focus:outline-none focus:transition-none focus-visible:rounded focus-visible:ring-2 focus-visible:ring-gray-400"
class="flex h-7 cursor-pointer items-center rounded text-gray-700 duration-300 ease-in-out focus:outline-none focus:transition-none focus-visible:rounded focus-visible:ring-2 focus-visible:ring-gray-400"
:class="isActive ? 'bg-white shadow-sm' : 'hover:bg-gray-100'"
@click="handleClick"
>
<div
class="flex w-full items-center justify-between duration-300 ease-in-out"
:class="isCollapsed ? 'p-1' : 'px-2 py-1'"
:class="isCollapsed ? 'ml-[3px] p-1' : 'px-2 py-1'"
>
<div class="flex items-center">
<Tooltip :text="label" placement="right" :disabled="!isCollapsed">
<slot name="icon">
<span class="grid h-5 w-6 flex-shrink-0 place-items-center">
<span class="grid h-4.5 w-4.5 flex-shrink-0 place-items-center">
<FeatherIcon
v-if="typeof icon == 'string'"
:name="icon"
class="h-4 w-4 text-gray-700"
class="h-4.5 w-4.5 text-gray-700"
/>
<component v-else :is="icon" class="h-4 w-4 text-gray-700" />
<component v-else :is="icon" class="h-4.5 w-4.5 text-gray-700" />
</span>
</slot>
</Tooltip>
<span
class="flex-1 flex-shrink-0 text-sm duration-300 ease-in-out"
class="flex-1 flex-shrink-0 text-base duration-300 ease-in-out"
:class="
isCollapsed
? 'ml-0 w-0 overflow-hidden opacity-0'

View File

@ -1,7 +1,7 @@
<template>
<NestedPopover>
<template #target>
<Button label="Sort" ref="sortButtonRef">
<Button :label="__('Sort')" ref="sortButtonRef">
<template #prefix><SortIcon class="h-4" /></template>
<template v-if="sortValues?.size" #suffix>
<div
@ -33,15 +33,15 @@
:value="sort.fieldname"
:options="sortOptions.data"
@change="(e) => updateSort(e, i)"
placeholder="Sort by"
:placeholder="__('First Name')"
/>
<FormControl
class="!w-32"
type="select"
v-model="sort.direction"
:options="[
{ label: 'Ascending', value: 'asc' },
{ label: 'Descending', value: 'desc' },
{ label: __('Ascending'), value: 'asc' },
{ label: __('Descending'), value: 'desc' },
]"
@change="
(e) => {
@ -49,7 +49,7 @@
apply()
}
"
placeholder="Sort by"
:placeholder="__('Ascending')"
/>
<Button variant="ghost" icon="x" @click="removeSort(i)" />
</div>
@ -58,13 +58,13 @@
v-else
class="mb-3 flex h-7 items-center px-3 text-sm text-gray-600"
>
Empty - Choose a field to sort by
{{ __('Empty - Choose a field to sort by') }}
</div>
<div class="flex items-center justify-between gap-2">
<Autocomplete
:options="options"
value=""
placeholder="Sort by"
:placeholder="__('First Name')"
@change="(e) => setSort(e)"
>
<template #target="{ togglePopover }">
@ -72,7 +72,7 @@
class="!text-gray-600"
variant="ghost"
@click="togglePopover()"
label="Add Sort"
:label="__('Add Sort')"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
@ -84,7 +84,7 @@
v-if="sortValues?.size"
class="!text-gray-600"
variant="ghost"
label="Clear Sort"
:label="__('Clear Sort')"
@click="clearSort(close)"
/>
</div>

View File

@ -1,29 +1,29 @@
<template>
<Dropdown :options="userDropdownOptions">
<Dropdown :options="dropdownOptions">
<template v-slot="{ open }">
<button
class="flex h-12 py-2 items-center rounded-md duration-300 ease-in-out"
class="flex h-12 items-center rounded-md py-2 duration-300 ease-in-out"
:class="
isCollapsed
? 'px-0 w-auto'
? 'w-auto px-0'
: open
? 'bg-white shadow-sm px-2 w-52'
: 'hover:bg-gray-200 px-2 w-52'
? 'w-52 bg-white px-2 shadow-sm'
: 'w-52 px-2 hover:bg-gray-200'
"
>
<CRMLogo class="w-8 h-8 rounded flex-shrink-0" />
<CRMLogo class="size-8 flex-shrink-0 rounded" />
<div
class="flex flex-1 flex-col text-left duration-300 ease-in-out"
:class="
isCollapsed
? 'opacity-0 ml-0 w-0 overflow-hidden'
: 'opacity-100 ml-2 w-auto'
? 'ml-0 w-0 overflow-hidden opacity-0'
: 'ml-2 w-auto opacity-100'
"
>
<div class="text-base font-medium text-gray-900 leading-none">
CRM
<div class="text-base font-medium leading-none text-gray-900">
{{ __('CRM') }}
</div>
<div class="mt-1 text-sm text-gray-700 leading-none">
<div class="mt-1 text-sm leading-none text-gray-700">
{{ user.full_name }}
</div>
</div>
@ -31,11 +31,15 @@
class="duration-300 ease-in-out"
:class="
isCollapsed
? 'opacity-0 ml-0 w-0 overflow-hidden'
: 'opacity-100 ml-2 w-auto'
? 'ml-0 w-0 overflow-hidden opacity-0'
: 'ml-2 w-auto opacity-100'
"
>
<FeatherIcon name="chevron-down" class="h-4 w-4 text-gray-600" aria-hidden="true" />
<FeatherIcon
name="chevron-down"
class="size-4 text-gray-600"
aria-hidden="true"
/>
</div>
</button>
</template>
@ -47,7 +51,7 @@ import CRMLogo from '@/components/Icons/CRMLogo.vue'
import { sessionStore } from '@/stores/session'
import { usersStore } from '@/stores/users'
import { Dropdown } from 'frappe-ui'
import { computed } from 'vue'
import { computed, ref } from 'vue'
const props = defineProps({
isCollapsed: {
@ -61,16 +65,16 @@ const { getUser } = usersStore()
const user = computed(() => getUser() || {})
const userDropdownOptions = [
let dropdownOptions = ref([
{
icon: 'corner-up-left',
label: 'Switch to Desk',
label: computed(() => __('Switch to Desk')),
onClick: () => window.location.replace('/app'),
},
{
icon: 'log-out',
label: 'Log out',
label: computed(() => __('Log out')),
onClick: () => logout.submit(),
},
]
])
</script>

View File

@ -3,7 +3,7 @@
<div class="flex items-center gap-2">
<Dropdown :options="viewsDropdownOptions">
<template #default="{ open }">
<Button :label="currentView.label">
<Button :label="__(currentView.label)">
<template #prefix>
<FeatherIcon :name="currentView.icon" class="h-4" />
</template>
@ -29,11 +29,11 @@
v-if="viewUpdated && route.query.view && (!view.public || isManager())"
class="flex items-center gap-2 border-r pr-2"
>
<Button label="Cancel" @click="cancelChanges" />
<Button label="Save Changes" @click="saveView" />
<Button :label="__('Cancel')" @click="cancelChanges" />
<Button :label="__('Save Changes')" @click="saveView" />
</div>
<div class="flex items-center gap-2">
<Button label="Refresh" @click="reload()" :loading="isLoading">
<Button :label="__('Refresh')" @click="reload()" :loading="isLoading">
<template #icon>
<RefreshIcon class="h-4 w-4" />
</template>
@ -55,11 +55,11 @@
v-if="!options.hideColumnsButton"
:options="[
{
group: 'Options',
group: __('Options'),
hideLabel: true,
items: [
{
label: 'Export',
label: __('Export'),
icon: () =>
h(FeatherIcon, { name: 'download', class: 'h-4 w-4' }),
onClick: () => (showExportDialog = true),
@ -96,10 +96,10 @@
<Dialog
v-model="showExportDialog"
:options="{
title: 'Export',
title: __('Export'),
actions: [
{
label: 'Download',
label: __('Download'),
variant: 'solid',
onClick: () => exportRows(),
},
@ -109,16 +109,25 @@
<template #body-content>
<FormControl
variant="outline"
label="Export Type"
:label="__('Export Type')"
type="select"
:options="['Excel', 'CSV']"
:options="[
{
label: __('Excel'),
value: 'Excel',
},
{
label: __('CSV'),
value: 'CSV',
},
]"
v-model="export_type"
placeholder="Select Export Type"
:placeholder="__('Excel')"
/>
<div class="mt-3">
<FormControl
type="checkbox"
:label="`Export All ${list.data.total_count} Records`"
:label="__('Export All {0} Record(s)', [list.data.total_count])"
v-model="export_all"
/>
</div>
@ -321,7 +330,7 @@ async function exportRows() {
const defaultViews = [
{
label: props.options?.defaultViewName || 'List View',
label: __(props.options?.defaultViewName) || __('List View'),
icon: 'list',
onClick() {
viewUpdated.value = false
@ -333,7 +342,7 @@ const defaultViews = [
const viewsDropdownOptions = computed(() => {
let _views = [
{
group: 'Default Views',
group: __('Default Views'),
hideLabel: true,
items: defaultViews,
},
@ -341,6 +350,7 @@ const viewsDropdownOptions = computed(() => {
if (list.value?.data?.views) {
list.value.data.views.forEach((view) => {
view.label = __(view.label)
view.icon = view.icon || 'list'
view.filters =
typeof view.filters == 'string'
@ -359,18 +369,18 @@ const viewsDropdownOptions = computed(() => {
publicViews.length &&
_views.push({
group: 'Public Views',
group: __('Public Views'),
items: publicViews,
})
savedViews.length &&
_views.push({
group: 'Saved Views',
group: __('Saved Views'),
items: savedViews,
})
pinnedViews.length &&
_views.push({
group: 'Pinned Views',
group: __('Pinned Views'),
items: pinnedViews,
})
}
@ -489,11 +499,11 @@ function updatePageLength(value, loadMore = false) {
const viewActions = computed(() => {
let actions = [
{
group: 'Default Views',
group: __('Default Views'),
hideLabel: true,
items: [
{
label: 'Duplicate',
label: __('Duplicate'),
icon: () => h(DuplicateIcon, { class: 'h-4 w-4' }),
onClick: () => duplicateView(),
},
@ -503,14 +513,14 @@ const viewActions = computed(() => {
if (route.query.view && (!view.value.public || isManager())) {
actions[0].items.push({
label: 'Rename',
label: __('Rename'),
icon: () => h(EditIcon, { class: 'h-4 w-4' }),
onClick: () => renameView(),
})
if (!view.value.public) {
actions[0].items.push({
label: view.value.pinned ? 'Unpin View' : 'Pin View',
label: view.value.pinned ? __('Unpin View') : __('Pin View'),
icon: () =>
h(view.value.pinned ? UnpinIcon : PinIcon, { class: 'h-4 w-4' }),
onClick: () => pinView(),
@ -519,7 +529,7 @@ const viewActions = computed(() => {
if (isManager()) {
actions[0].items.push({
label: view.value.public ? 'Make Private' : 'Make Public',
label: view.value.public ? __('Make Private') : __('Make Public'),
icon: () =>
h(FeatherIcon, {
name: view.value.public ? 'lock' : 'unlock',
@ -530,20 +540,20 @@ const viewActions = computed(() => {
}
actions.push({
group: 'Delete View',
group: __('Delete View'),
hideLabel: true,
items: [
{
label: 'Delete',
label: __('Delete'),
icon: 'trash-2',
onClick: () =>
$dialog({
title: 'Delete View',
message: 'Are you sure you want to delete this view?',
title: __('Delete View'),
message: __('Are you sure you want to delete this view?'),
variant: 'danger',
actions: [
{
label: 'Delete',
label: __('Delete'),
variant: 'solid',
theme: 'red',
onClick: (close) => deleteView(close),
@ -558,15 +568,15 @@ const viewActions = computed(() => {
})
function duplicateView() {
let label = getView(route.query.view)?.label || 'List View'
let label = __(getView(route.query.view)?.label) || __('List View')
view.value.name = ''
view.value.label = label + ' (New)'
view.value.label = label + __(' (New)')
showViewModal.value = true
}
function renameView() {
view.value.name = route.query.view
view.value.label = getView(route.query.view).label
view.value.label = __(getView(route.query.view).label)
showViewModal.value = true
}

View File

@ -19,6 +19,7 @@ import {
frappeRequest,
FeatherIcon,
} from 'frappe-ui'
import translationPlugin from './translation'
import { createDialog } from './utils/dialogs'
import socket from './socket'
import { getCachedListResource } from 'frappe-ui/src/resources/listResource'
@ -45,6 +46,7 @@ setConfig('resourceFetcher', frappeRequest)
app.use(FrappeUI)
app.use(pinia)
app.use(router)
app.use(translationPlugin)
for (let key in globalComponents) {
app.component(key, globalComponents[key])
}

View File

@ -7,16 +7,15 @@
<Button
v-if="callLog.data.type == 'Incoming' && !callLog.data.lead"
variant="solid"
label="Create lead"
:label="__('Create lead')"
@click="createLead"
>
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</template>
</LayoutHeader>
<div class="border-b"></div>
<div v-if="callLog.data" class="max-w-lg p-6">
<div class="pb-3 text-base font-medium">Call details</div>
<div class="pb-3 text-base font-medium">{{ __('Call details') }}</div>
<div class="mb-3 flex flex-col gap-4 rounded-lg border p-4 shadow-sm">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
@ -29,7 +28,11 @@
class="h-4 w-4 text-gray-600"
/>
<div class="font-medium">
{{ callLog.data.type == 'Incoming' ? 'Inbound' : 'Outbound' }} call
{{
callLog.data.type == 'Incoming'
? __('Inbound Call')
: __('Outbound Call')
}}
</div>
</div>
<div>
@ -37,7 +40,7 @@
:variant="'subtle'"
:theme="statusColorMap[callLog.data.status]"
size="md"
:label="statusLabelMap[callLog.data.status]"
:label="__(statusLabelMap[callLog.data.status])"
/>
</div>
</div>
@ -50,7 +53,7 @@
/>
<div class="ml-1 flex flex-col gap-1">
<div class="text-base font-medium">
{{ callLog.data.caller.label }}
{{ __(callLog.data.caller.label) }}
</div>
<div class="text-xs text-gray-600">
{{ callLog.data.from }}
@ -64,7 +67,7 @@
/>
<div class="ml-1 flex flex-col gap-1">
<div class="text-base font-medium">
{{ callLog.data.receiver.label }}
{{ __(callLog.data.receiver.label) }}
</div>
<div class="text-xs text-gray-600">
{{ callLog.data.to }}
@ -75,19 +78,19 @@
<div class="flex items-center justify-between">
<div class="flex items-center gap-1">
<DurationIcon class="h-4 w-4 text-gray-600" />
<div class="text-sm text-gray-600">Duration</div>
<div class="text-sm text-gray-600">{{ __('Duration') }}</div>
<div class="text-sm">{{ callLog.data.duration }}</div>
</div>
<Tooltip :text="dateFormat(callLog.data.creation, dateTooltipFormat)">
<div class="text-sm text-gray-600">
{{ timeAgo(callLog.data.creation) }}
{{ __(timeAgo(callLog.data.creation)) }}
</div>
</Tooltip>
</div>
</div>
<div v-if="callLog.data.recording_url" class="mt-6">
<div class="mb-3 text-base font-medium">Call recording</div>
<div class="mb-3 text-base font-medium">{{ __('Call recording') }}</div>
<div class="flex items-center justify-between rounded border shadow-sm">
<audio
class="audio-control"
@ -98,7 +101,7 @@
</div>
<div v-if="callLog.data.note" class="mt-6">
<div class="mb-3 text-base font-medium">Call note</div>
<div class="mb-3 text-base font-medium">{{ __('Call note') }}</div>
<div
class="flex h-56 cursor-pointer flex-col gap-3 rounded border p-4 shadow-sm"
@click="showNoteModal = true"
@ -117,7 +120,7 @@
</div>
<div v-if="callLog.data.lead" class="mt-6">
<div class="mb-3 text-base font-medium">Lead</div>
<div class="mb-3 text-base font-medium">{{ __('Lead') }}</div>
<Button
variant="outline"
@ -206,7 +209,7 @@ function createLead() {
}
const breadcrumbs = computed(() => {
let items = [{ label: 'Call Logs', route: { name: 'Call Logs' } }]
let items = [{ label: __('Call Logs'), route: { name: 'Call Logs' } }]
items.push({
label: callLog.data?.caller.label,
route: { name: 'Call Log', params: { callLogId: props.callLogId } },

View File

@ -44,7 +44,7 @@
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
>
<PhoneIcon class="h-10 w-10" />
<span>No Logs Found</span>
<span>{{ __('No Logs Found') }}</span>
</div>
</div>
</template>
@ -69,7 +69,7 @@ import { computed, ref } from 'vue'
const { getUser } = usersStore()
const { getContact, getLeadContact } = contactsStore()
const breadcrumbs = [{ label: 'Call Logs', route: { name: 'Call Logs' } }]
const breadcrumbs = [{ label: __('Call Logs'), route: { name: 'Call Logs' } }]
const callLogsListView = ref(null)
@ -131,7 +131,7 @@ const rows = computed(() => {
} else if (['modified', 'creation'].includes(row)) {
_rows[row] = {
label: dateFormat(callLog[row], dateTooltipFormat),
timeAgo: timeAgo(callLog[row]),
timeAgo: __(timeAgo(callLog[row])),
}
}
})

View File

@ -24,13 +24,13 @@
{
icon: 'upload',
label: contact.data.image
? 'Change image'
: 'Upload image',
? __('Change image')
: __('Upload image'),
onClick: openFileSelector,
},
{
icon: 'trash-2',
label: 'Remove image',
label: __('Remove image'),
onClick: () => changeContactImage(''),
},
],
@ -71,7 +71,10 @@
>
&middot;
</span>
<Tooltip text="Make Call" v-if="contact.data.actual_mobile_no">
<Tooltip
:text="__('Make Call')"
v-if="contact.data.actual_mobile_no"
>
<div
class="flex cursor-pointer items-center gap-1.5"
@click="makeCall(contact.data.actual_mobile_no)"
@ -113,7 +116,7 @@
contact.data.company_name
"
variant="ghost"
label="More"
:label="__('More')"
class="-ml-1 cursor-pointer hover:text-gray-900"
@click="
() => {
@ -125,7 +128,7 @@
</div>
<div class="mt-2 flex gap-1.5">
<Button
label="Edit"
:label="__('Edit')"
size="sm"
@click="
() => {
@ -139,7 +142,7 @@
</template>
</Button>
<Button
label="Delete"
:label="__('Delete')"
theme="red"
size="sm"
@click="deleteContact"
@ -149,7 +152,7 @@
</template>
</Button>
</div>
<ErrorMessage :message="error" />
<ErrorMessage :message="__(error)" />
</div>
</div>
</template>
@ -161,7 +164,7 @@
:class="{ 'text-gray-900': selected }"
>
<component v-if="tab.icon" :is="tab.icon" class="h-5" />
{{ tab.label }}
{{ __(tab.label) }}
<Badge
class="group-hover:bg-gray-900"
:class="[selected ? 'bg-gray-900' : 'bg-gray-600']"
@ -187,7 +190,7 @@
>
<div class="flex flex-col items-center justify-center space-y-3">
<component :is="tab.icon" class="!h-10 !w-10" />
<div>No {{ tab.label }} Found</div>
<div>{{ __('No {0} Found', [__(tab.label)]) }}</div>
</div>
</div>
</template>
@ -251,7 +254,7 @@ const showContactModal = ref(false)
const detailMode = ref(false)
const breadcrumbs = computed(() => {
let items = [{ label: 'Contacts', route: { name: 'Contacts' } }]
let items = [{ label: __('Contacts'), route: { name: 'Contacts' } }]
items.push({
label: contact.data?.full_name,
route: { name: 'Contact', params: { contactId: props.contactId } },
@ -262,7 +265,7 @@ const breadcrumbs = computed(() => {
function validateFile(file) {
let extn = file.name.split('.').pop().toLowerCase()
if (!['png', 'jpg', 'jpeg'].includes(extn)) {
return 'Only PNG and JPG images are allowed'
return __('Only PNG and JPG images are allowed')
}
}
@ -278,11 +281,11 @@ async function changeContactImage(file) {
async function deleteContact() {
$dialog({
title: 'Delete contact',
message: 'Are you sure you want to delete this contact?',
title: __('Delete contact'),
message: __('Are you sure you want to delete this contact?'),
actions: [
{
label: 'Delete',
label: __('Delete'),
theme: 'red',
variant: 'solid',
async onClick(close) {
@ -360,44 +363,44 @@ function getDealRowObject(deal) {
},
modified: {
label: dateFormat(deal.modified, dateTooltipFormat),
timeAgo: timeAgo(deal.modified),
timeAgo: __(timeAgo(deal.modified)),
},
}
}
const dealColumns = [
{
label: 'Organization',
label: __('Organization'),
key: 'organization',
width: '11rem',
},
{
label: 'Amount',
label: __('Amount'),
key: 'annual_revenue',
width: '9rem',
},
{
label: 'Status',
label: __('Status'),
key: 'status',
width: '10rem',
},
{
label: 'Email',
label: __('Email'),
key: 'email',
width: '12rem',
},
{
label: 'Mobile no',
label: __('Mobile no'),
key: 'mobile_no',
width: '11rem',
},
{
label: 'Deal owner',
label: __('Deal owner'),
key: 'deal_owner',
width: '10rem',
},
{
label: 'Last modified',
label: __('Last modified'),
key: 'modified',
width: '8rem',
},

View File

@ -8,7 +8,11 @@
v-if="contactsListView?.customListActions"
:actions="contactsListView.customListActions"
/>
<Button variant="solid" label="Create" @click="showContactModal = true">
<Button
variant="solid"
:label="__('Create')"
@click="showContactModal = true"
>
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</template>
@ -47,8 +51,8 @@
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
>
<ContactsIcon class="h-10 w-10" />
<span>No Contacts Found</span>
<Button label="Create" @click="showContactModal = true">
<span>{{ __('No Contacts Found') }}</span>
<Button :label="__('Create')" @click="showContactModal = true">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</div>
@ -81,10 +85,10 @@ const currentContact = computed(() => {
})
const breadcrumbs = computed(() => {
let items = [{ label: 'Contacts', route: { name: 'Contacts' } }]
let items = [{ label: __('Contacts'), route: { name: 'Contacts' } }]
if (!currentContact.value) return items
items.push({
label: currentContact.value.full_name,
label: __(currentContact.value.full_name),
route: {
name: 'Contact',
params: { contactId: currentContact.value.name },
@ -123,7 +127,7 @@ const rows = computed(() => {
} else if (['modified', 'creation'].includes(row)) {
_rows[row] = {
label: dateFormat(contact[row], dateTooltipFormat),
timeAgo: timeAgo(contact[row]),
timeAgo: __(timeAgo(contact[row])),
}
}
})

View File

@ -39,7 +39,7 @@
<Activities
ref="activities"
doctype="CRM Deal"
:title="tab.label"
:title="tab.name"
v-model:reload="reload"
v-model="deal"
/>
@ -48,10 +48,10 @@
<div
class="flex h-10.5 items-center border-b px-5 py-2.5 text-lg font-semibold"
>
About this Deal
{{ __('About this Deal') }}
</div>
<div class="flex items-center justify-start gap-5 border-b p-5">
<Tooltip text="Organization logo">
<Tooltip :text="__('Organization logo')">
<div class="group relative h-[88px] w-[88px]">
<Avatar
size="3xl"
@ -68,31 +68,31 @@
</div>
</Tooltip>
<div class="flex gap-1.5">
<Tooltip text="Make a call">
<Tooltip :text="__('Make a call')">
<Button class="h-7 w-7" @click="triggerCall">
<PhoneIcon class="h-4 w-4" />
</Button>
</Tooltip>
<Tooltip text="Send an email">
<Tooltip :text="__('Send an email')">
<Button class="h-7 w-7">
<EmailIcon
class="h-4 w-4"
@click="
deal.data.email
? openEmailBox()
: errorMessage('No email set')
: errorMessage(__('No email set'))
"
/>
</Button>
</Tooltip>
<Tooltip text="Go to website">
<Tooltip :text="__('Go to website')">
<Button class="h-7 w-7">
<LinkIcon
class="h-4 w-4"
@click="
deal.data.website
? openWebsite(deal.data.website)
: errorMessage('No website set')
: errorMessage(__('No website set'))
"
/>
</Button>
@ -137,7 +137,7 @@
<template #target="{ togglePopover }">
<Button
class="h-7 px-3"
label="Add Contact"
:label="__('Add Contact')"
@click="togglePopover()"
>
<template #prefix>
@ -162,7 +162,7 @@
class="flex min-h-20 flex-1 items-center justify-center gap-3 text-base text-gray-500"
>
<LoadingIndicator class="h-4 w-4" />
<span>Loading...</span>
<span>{{ __('Loading...') }}</span>
</div>
<div
v-else-if="section.contacts.length"
@ -194,7 +194,7 @@
v-if="contact.is_primary"
class="ml-2"
variant="outline"
label="Primary"
:label="__('Primary')"
theme="green"
/>
</div>
@ -251,7 +251,7 @@
v-else
class="flex h-20 items-center justify-center text-base text-gray-600"
>
No contacts added
{{ __('No contacts added') }}
</div>
</div>
</Section>
@ -392,7 +392,7 @@ function updateDeal(fieldname, value, callback) {
deal.reload()
reload.value = true
createToast({
title: 'Deal updated',
title: __('Deal updated'),
icon: 'check',
iconClasses: 'text-green-600',
})
@ -400,8 +400,8 @@ function updateDeal(fieldname, value, callback) {
},
onError: (err) => {
createToast({
title: 'Error updating deal',
text: err.messages?.[0],
title: __('Error updating deal'),
text: __(err.messages?.[0]),
icon: 'x',
iconClasses: 'text-red-600',
})
@ -413,8 +413,8 @@ function validateRequired(fieldname, value) {
let meta = deal.data.all_fields || {}
if (meta[fieldname]?.reqd && !value) {
createToast({
title: 'Error Updating Deal',
text: `${meta[fieldname].label} is a required field`,
title: __('Error Updating Deal'),
text: __('{0} is a required field', [meta[fieldname].label]),
icon: 'x',
iconClasses: 'text-red-600',
})
@ -424,7 +424,7 @@ function validateRequired(fieldname, value) {
}
const breadcrumbs = computed(() => {
let items = [{ label: 'Deals', route: { name: 'Deals' } }]
let items = [{ label: __('Deals'), route: { name: 'Deals' } }]
items.push({
label: organization.value?.name,
route: { name: 'Deal', params: { dealId: deal.data.name } },
@ -435,22 +435,27 @@ const breadcrumbs = computed(() => {
const tabIndex = ref(0)
const tabs = [
{
name: 'Activity',
label: 'Activity',
icon: ActivityIcon,
},
{
name: 'Emails',
label: 'Emails',
icon: EmailIcon,
},
{
name: 'Calls',
label: 'Calls',
icon: PhoneIcon,
},
{
name: 'Tasks',
label: 'Tasks',
icon: TaskIcon,
},
{
name: 'Notes',
label: 'Notes',
icon: NoteIcon,
},
@ -504,7 +509,7 @@ const _contact = ref({})
function contactOptions(contact) {
let options = [
{
label: 'Delete',
label: __('Delete'),
icon: 'trash-2',
onClick: () => removeContact(contact),
},
@ -512,7 +517,7 @@ function contactOptions(contact) {
if (!contact.is_primary) {
options.push({
label: 'Set as Primary Contact',
label: __('Set as Primary Contact'),
icon: h(SuccessIcon, { class: 'h-4 w-4' }),
onClick: () => setPrimaryContact(contact),
})
@ -529,7 +534,7 @@ async function addContact(contact) {
if (d) {
deal_contacts.reload()
createToast({
title: 'Contact added',
title: __('Contact added'),
icon: 'check',
iconClasses: 'text-green-600',
})
@ -544,7 +549,7 @@ async function removeContact(contact) {
if (d) {
deal_contacts.reload()
createToast({
title: 'Contact removed',
title: __('Contact removed'),
icon: 'check',
iconClasses: 'text-green-600',
})
@ -559,7 +564,7 @@ async function setPrimaryContact(contact) {
if (d) {
deal_contacts.reload()
createToast({
title: 'Primary contact set',
title: __('Primary contact set'),
icon: 'check',
iconClasses: 'text-green-600',
})
@ -578,12 +583,12 @@ function triggerCall() {
let mobile_no = primaryContact.mobile_no || null
if (!primaryContact) {
errorMessage('No primary contact set')
errorMessage(__('No primary contact set'))
return
}
if (!mobile_no) {
errorMessage('No mobile number set')
errorMessage(__('No mobile number set'))
return
}

View File

@ -8,7 +8,11 @@
v-if="dealsListView?.customListActions"
:actions="dealsListView.customListActions"
/>
<Button variant="solid" label="Create" @click="showNewDialog = true">
<Button
variant="solid"
:label="__('Create')"
@click="showNewDialog = true"
>
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</template>
@ -44,8 +48,8 @@
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
>
<DealsIcon class="h-10 w-10" />
<span>No Deals Found</span>
<Button label="Create" @click="showNewDialog = true">
<span>{{ __('No Deals Found') }}</span>
<Button :label="__('Create')" @click="showNewDialog = true">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</div>
@ -54,8 +58,7 @@
v-model="showNewDialog"
:options="{
size: '3xl',
title: 'New Deal',
actions: [{ label: 'Save', variant: 'solid' }],
title: __('New Deal'),
}"
>
<template #body-content>
@ -63,7 +66,11 @@
</template>
<template #actions="{ close }">
<div class="flex flex-row-reverse gap-2">
<Button variant="solid" label="Save" @click="createNewDeal(close)" />
<Button
variant="solid"
:label="__('Save')"
@click="createNewDeal(close)"
/>
</div>
</template>
</Dialog>
@ -85,12 +92,13 @@ import {
timeAgo,
formatNumberIntoCurrency,
formatTime,
createToast,
} from '@/utils'
import { createResource, Breadcrumbs } from 'frappe-ui'
import { useRouter } from 'vue-router'
import { ref, computed, reactive } from 'vue'
const breadcrumbs = [{ label: 'Deals', route: { name: 'Deals' } }]
const breadcrumbs = [{ label: __('Deals'), route: { name: 'Deals' } }]
const { getUser } = usersStore()
const { getOrganization } = organizationsStore()
@ -139,7 +147,7 @@ const rows = computed(() => {
? 'green'
: 'orange'
if (value == 'First Response Due') {
value = timeAgo(deal.response_by)
value = __(timeAgo(deal.response_by))
tooltipText = dateFormat(deal.response_by, dateTooltipFormat)
if (new Date(deal.response_by) < new Date()) {
color = 'red'
@ -168,7 +176,7 @@ const rows = computed(() => {
} else if (['modified', 'creation'].includes(row)) {
_rows[row] = {
label: dateFormat(deal[row], dateTooltipFormat),
timeAgo: timeAgo(deal[row]),
timeAgo: __(timeAgo(deal[row])),
}
} else if (
['first_response_time', 'first_responded_on', 'response_by'].includes(
@ -181,7 +189,7 @@ const rows = computed(() => {
timeAgo: deal[row]
? row == 'first_response_time'
? formatTime(deal[row])
: timeAgo(deal[row])
: __(timeAgo(deal[row]))
: '',
}
}
@ -218,7 +226,13 @@ function createNewDeal(close) {
.submit(newDeal, {
validate() {
if (!newDeal.first_name) {
return 'First name is required'
createToast({
title: __('Error creating deal'),
text: __('First name is required'),
icon: 'x',
iconClasses: 'text-red-600',
})
return __('First name is required')
}
},
onSuccess(data) {

View File

@ -8,7 +8,11 @@
v-if="emailTemplatesListView?.customListActions"
:actions="emailTemplatesListView.customListActions"
/>
<Button variant="solid" label="Create" @click="showEmailTemplateModal = true">
<Button
variant="solid"
:label="__('Create')"
@click="showEmailTemplateModal = true"
>
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</template>
@ -48,7 +52,10 @@
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
>
<EmailIcon class="h-10 w-10" />
<span>No Email Templates Found</span>
<span>{{ __('No Email Templates Found') }}</span>
<Button :label="__('Create')" @click="showEmailTemplateModal = true">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</div>
</div>
<EmailTemplateModal
@ -70,7 +77,7 @@ import { Breadcrumbs } from 'frappe-ui'
import { computed, ref } from 'vue'
const breadcrumbs = [
{ label: 'Email Templates', route: { name: 'Email Templates' } },
{ label: __('Email Templates'), route: { name: 'Email Templates' } },
]
const emailTemplatesListView = ref(null)
@ -103,12 +110,12 @@ const rows = computed(() => {
const showEmailTemplateModal = ref(false)
const emailTemplate = ref({
subject: '',
response: '',
name: '',
enabled: 1,
owner: '',
reference_doctype: 'CRM Deal',
subject: '',
response: '',
name: '',
enabled: 1,
owner: '',
reference_doctype: 'CRM Deal',
})
function showEmailTemplate(name) {

View File

@ -33,7 +33,7 @@
</template>
</Dropdown>
<Button
label="Convert to Deal"
:label="__('Convert to Deal')"
variant="solid"
@click="showConvertToDealModal = true"
/>
@ -44,7 +44,7 @@
<Activities
ref="activities"
doctype="CRM Lead"
:title="tab.label"
:title="tab.name"
v-model:reload="reload"
v-model="lead"
/>
@ -53,7 +53,7 @@
<div
class="flex h-10.5 items-center border-b px-5 py-2.5 text-lg font-semibold"
>
About this Lead
{{ __('About this Lead') }}
</div>
<FileUploader
@success="(file) => updateField('image', file.file_url)"
@ -77,13 +77,13 @@
{
icon: 'upload',
label: lead.data.image
? 'Change image'
: 'Upload image',
? __('Change image')
: __('Upload image'),
onClick: openFileSelector,
},
{
icon: 'trash-2',
label: 'Remove image',
label: __('Remove image'),
onClick: () => updateField('image', ''),
},
],
@ -110,45 +110,45 @@
</div>
</Tooltip>
<div class="flex gap-1.5">
<Tooltip text="Make a call">
<Tooltip :text="__('Make a call')">
<Button
class="h-7 w-7"
@click="
() =>
lead.data.mobile_no
? makeCall(lead.data.mobile_no)
: errorMessage('No phone number set')
: errorMessage(__('No phone number set'))
"
>
<PhoneIcon class="h-4 w-4" />
</Button>
</Tooltip>
<Tooltip text="Send an email">
<Tooltip :text="__('Send an email')">
<Button class="h-7 w-7">
<EmailIcon
class="h-4 w-4"
@click="
lead.data.email
? openEmailBox()
: errorMessage('No email set')
: errorMessage(__('No email set'))
"
/>
</Button>
</Tooltip>
<Tooltip text="Go to website">
<Tooltip :text="__('Go to website')">
<Button class="h-7 w-7">
<LinkIcon
class="h-4 w-4"
@click="
lead.data.website
? openWebsite(lead.data.website)
: errorMessage('No website set')
: errorMessage(__('No website set'))
"
/>
</Button>
</Tooltip>
</div>
<ErrorMessage :message="error" />
<ErrorMessage :message="__(error)" />
</div>
</div>
</template>
@ -190,11 +190,11 @@
<Dialog
v-model="showConvertToDealModal"
:options="{
title: 'Convert to Deal',
title: __('Convert to Deal'),
size: 'xl',
actions: [
{
label: 'Convert',
label: __('Convert'),
variant: 'solid',
onClick: convertToDeal,
},
@ -204,11 +204,11 @@
<template #body-content>
<div class="mb-4 flex items-center gap-2 text-gray-600">
<OrganizationsIcon class="h-4 w-4" />
<label class="block text-base"> Organization </label>
<label class="block text-base">{{ __('Organization') }}</label>
</div>
<div class="ml-6">
<div class="flex items-center justify-between text-base">
<div>Choose Existing</div>
<div>{{ __('Choose Existing') }}</div>
<Switch v-model="existingOrganizationChecked" />
</div>
<Link
@ -221,17 +221,21 @@
@change="(data) => (existingOrganization = data)"
/>
<div v-else class="mt-2.5 text-base">
New organization will be created based on the data in details section
{{
__(
'New organization will be created based on the data in details section'
)
}}
</div>
</div>
<div class="mb-4 mt-6 flex items-center gap-2 text-gray-600">
<ContactsIcon class="h-4 w-4" />
<label class="block text-base"> Contact </label>
<label class="block text-base">{{ __('Contact') }}</label>
</div>
<div class="ml-6">
<div class="flex items-center justify-between text-base">
<div>Choose Existing</div>
<div>{{ __('Choose Existing') }}</div>
<Switch v-model="existingContactChecked" />
</div>
<Link
@ -244,7 +248,7 @@
@change="(data) => (existingContact = data)"
/>
<div v-else class="mt-2.5 text-base">
New contact will be created based on the person's details
{{ __("New contact will be created based on the person's details") }}
</div>
</div>
</template>
@ -352,7 +356,7 @@ function updateLead(fieldname, value, callback) {
lead.reload()
reload.value = true
createToast({
title: 'Lead updated',
title: __('Lead updated'),
icon: 'check',
iconClasses: 'text-green-600',
})
@ -360,8 +364,8 @@ function updateLead(fieldname, value, callback) {
},
onError: (err) => {
createToast({
title: 'Error updating lead',
text: err.messages?.[0],
title: __('Error updating lead'),
text: __(err.messages?.[0]),
icon: 'x',
iconClasses: 'text-red-600',
})
@ -373,8 +377,8 @@ function validateRequired(fieldname, value) {
let meta = lead.data.all_fields || {}
if (meta[fieldname]?.reqd && !value) {
createToast({
title: 'Error Updating Lead',
text: `${meta[fieldname].label} is a required field`,
title: __('Error Updating Lead'),
text: __('{0} is a required field', [meta[fieldname].label]),
icon: 'x',
iconClasses: 'text-red-600',
})
@ -384,7 +388,7 @@ function validateRequired(fieldname, value) {
}
const breadcrumbs = computed(() => {
let items = [{ label: 'Leads', route: { name: 'Leads' } }]
let items = [{ label: __('Leads'), route: { name: 'Leads' } }]
items.push({
label: lead.data.lead_name,
route: { name: 'Lead', params: { leadId: lead.data.name } },
@ -395,23 +399,28 @@ const breadcrumbs = computed(() => {
const tabIndex = ref(0)
const tabs = [
{
label: 'Activity',
name: 'Activity',
label: __('Activity'),
icon: ActivityIcon,
},
{
label: 'Emails',
name: 'Emails',
label: __('Emails'),
icon: EmailIcon,
},
{
label: 'Calls',
name: 'Calls',
label: __('Calls'),
icon: PhoneIcon,
},
{
label: 'Tasks',
name: 'Tasks',
label: __('Tasks'),
icon: TaskIcon,
},
{
label: 'Notes',
name: 'Notes',
label: __('Notes'),
icon: NoteIcon,
},
]
@ -419,7 +428,7 @@ const tabs = [
function validateFile(file) {
let extn = file.name.split('.').pop().toLowerCase()
if (!['png', 'jpg', 'jpeg'].includes(extn)) {
return 'Only PNG and JPG images are allowed'
return __('Only PNG and JPG images are allowed')
}
}
@ -457,8 +466,8 @@ async function convertToDeal(updated) {
if (existingContactChecked.value && !existingContact.value) {
createToast({
title: 'Error',
text: 'Please select an existing contact',
title: __('Error'),
text: __('Please select an existing contact'),
icon: 'x',
iconClasses: 'text-red-600',
})
@ -467,8 +476,8 @@ async function convertToDeal(updated) {
if (existingOrganizationChecked.value && !existingOrganization.value) {
createToast({
title: 'Error',
text: 'Please select an existing organization',
title: __('Error'),
text: __('Please select an existing organization'),
icon: 'x',
iconClasses: 'text-red-600',
})

View File

@ -8,7 +8,11 @@
v-if="leadsListView?.customListActions"
:actions="leadsListView.customListActions"
/>
<Button variant="solid" label="Create" @click="showNewDialog = true">
<Button
variant="solid"
:label="__('Create')"
@click="showNewDialog = true"
>
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</template>
@ -45,8 +49,8 @@
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
>
<LeadsIcon class="h-10 w-10" />
<span>No Leads Found</span>
<Button label="Create" @click="showNewDialog = true">
<span>{{ __('No Leads Found') }}</span>
<Button :label="__('Create')" @click="showNewDialog = true">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</div>
@ -55,8 +59,7 @@
v-model="showNewDialog"
:options="{
size: '3xl',
title: 'New Lead',
actions: [{ label: 'Save', variant: 'solid' }],
title: __('New Lead'),
}"
>
<template #body-content>
@ -64,7 +67,11 @@
</template>
<template #actions="{ close }">
<div class="flex flex-row-reverse gap-2">
<Button variant="solid" label="Save" @click="createNewLead(close)" />
<Button
variant="solid"
:label="__('Save')"
@click="createNewLead(close)"
/>
</div>
</template>
</Dialog>
@ -80,12 +87,18 @@ import ViewControls from '@/components/ViewControls.vue'
import { usersStore } from '@/stores/users'
import { organizationsStore } from '@/stores/organizations'
import { statusesStore } from '@/stores/statuses'
import { dateFormat, dateTooltipFormat, timeAgo, formatTime } from '@/utils'
import {
dateFormat,
dateTooltipFormat,
timeAgo,
formatTime,
createToast,
} from '@/utils'
import { createResource, Breadcrumbs } from 'frappe-ui'
import { useRouter } from 'vue-router'
import { ref, computed, reactive } from 'vue'
const breadcrumbs = [{ label: 'Leads', route: { name: 'Leads' } }]
const breadcrumbs = [{ label: __('Leads'), route: { name: 'Leads' } }]
const { getUser } = usersStore()
const { getOrganization } = organizationsStore()
@ -136,7 +149,7 @@ const rows = computed(() => {
? 'green'
: 'orange'
if (value == 'First Response Due') {
value = timeAgo(lead.response_by)
value = __(timeAgo(lead.response_by))
tooltipText = dateFormat(lead.response_by, dateTooltipFormat)
if (new Date(lead.response_by) < new Date()) {
color = 'red'
@ -165,7 +178,7 @@ const rows = computed(() => {
} else if (['modified', 'creation'].includes(row)) {
_rows[row] = {
label: dateFormat(lead[row], dateTooltipFormat),
timeAgo: timeAgo(lead[row]),
timeAgo: __(timeAgo(lead[row])),
}
} else if (
['first_response_time', 'first_responded_on', 'response_by'].includes(
@ -178,7 +191,7 @@ const rows = computed(() => {
timeAgo: lead[row]
? row == 'first_response_time'
? formatTime(lead[row])
: timeAgo(lead[row])
: __(timeAgo(lead[row]))
: '',
}
}
@ -219,7 +232,13 @@ function createNewLead(close) {
.submit(newLead, {
validate() {
if (!newLead.first_name) {
return 'First name is required'
createToast({
title: __('Error creating lead'),
text: __('First name is required'),
icon: 'x',
iconClasses: 'text-red-600',
})
return __('First name is required')
}
},
onSuccess(data) {

View File

@ -4,7 +4,7 @@
<Breadcrumbs :items="breadcrumbs" />
</template>
<template #right-header>
<Button variant="solid" label="Create" @click="createNote">
<Button variant="solid" :label="__('Create')" @click="createNote">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</template>
@ -16,7 +16,7 @@
doctype="FCRM Note"
:options="{
hideColumnsButton: true,
defaultViewName: 'Notes View',
defaultViewName: __('Notes View'),
}"
/>
<div class="flex-1 overflow-y-auto">
@ -36,8 +36,8 @@
<Dropdown
:options="[
{
label: __('Delete'),
icon: 'trash-2',
label: 'Delete',
onClick: () => deleteNote(note.name),
},
]"
@ -66,7 +66,7 @@
</div>
<Tooltip :text="dateFormat(note.modified, dateTooltipFormat)">
<div class="text-sm text-gray-700">
{{ timeAgo(note.modified) }}
{{ __(timeAgo(note.modified)) }}
</div>
</Tooltip>
</div>
@ -88,8 +88,8 @@
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
>
<NoteIcon class="h-10 w-10" />
<span>No Notes Found</span>
<Button label="Create" @click="createNote">
<span>{{ __('No Notes Found') }}</span>
<Button :label="__('Create')" @click="createNote">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</div>
@ -121,13 +121,7 @@ import { ref, watch } from 'vue'
const { getUser } = usersStore()
const list = {
title: 'Notes',
plural_label: 'Notes',
singular_label: 'Note',
}
const breadcrumbs = [{ label: list.title, route: { name: 'Notes' } }]
const breadcrumbs = [{ label: __('Notes'), route: { name: 'Notes' } }]
const showNoteModal = ref(false)
const currentNote = ref(null)

View File

@ -27,13 +27,13 @@
{
icon: 'upload',
label: organization.doc.organization_logo
? 'Change image'
: 'Upload image',
? __('Change image')
: __('Upload image'),
onClick: openFileSelector,
},
{
icon: 'trash-2',
label: 'Remove image',
label: __('Remove image'),
onClick: () => changeOrganizationImage(''),
},
],
@ -118,7 +118,7 @@
organization.doc.annual_revenue
"
variant="ghost"
label="More"
:label="__('More')"
class="-ml-1 cursor-pointer hover:text-gray-900"
@click="
() => {
@ -130,7 +130,7 @@
</div>
<div class="mt-2 flex gap-1.5">
<Button
label="Edit"
:label="__('Edit')"
size="sm"
@click="
() => {
@ -144,7 +144,7 @@
</template>
</Button>
<Button
label="Delete"
:label="__('Delete')"
theme="red"
size="sm"
@click="deleteOrganization"
@ -154,7 +154,7 @@
</template>
</Button>
</div>
<ErrorMessage class="mt-2" :message="error" />
<ErrorMessage class="mt-2" :message="__(error)" />
</div>
</div>
</template>
@ -166,7 +166,7 @@
:class="{ 'text-gray-900': selected }"
>
<component v-if="tab.icon" :is="tab.icon" class="h-5" />
{{ tab.label }}
{{ __(tab.label) }}
<Badge
class="group-hover:bg-gray-900"
:class="[selected ? 'bg-gray-900' : 'bg-gray-600']"
@ -199,7 +199,7 @@
>
<div class="flex flex-col items-center justify-center space-y-3">
<component :is="tab.icon" class="!h-10 !w-10" />
<div>No {{ tab.label }} Found</div>
<div>{{ __('No {0} Found', [__(tab.label)]) }}</div>
</div>
</div>
</template>
@ -268,7 +268,7 @@ const organization = createDocumentResource({
})
const breadcrumbs = computed(() => {
let items = [{ label: 'Organizations', route: { name: 'Organizations' } }]
let items = [{ label: __('Organizations'), route: { name: 'Organizations' } }]
items.push({
label: props.organizationId,
route: {
@ -282,7 +282,7 @@ const breadcrumbs = computed(() => {
function validateFile(file) {
let extn = file.name.split('.').pop().toLowerCase()
if (!['png', 'jpg', 'jpeg'].includes(extn)) {
return 'Only PNG and JPG images are allowed'
return __('Only PNG and JPG images are allowed')
}
}
@ -298,11 +298,11 @@ async function changeOrganizationImage(file) {
async function deleteOrganization() {
$dialog({
title: 'Delete organization',
message: 'Are you sure you want to delete this organization?',
title: __('Delete organization'),
message: __('Are you sure you want to delete this organization?'),
actions: [
{
label: 'Delete',
label: __('Delete'),
theme: 'red',
variant: 'solid',
async onClick(close) {
@ -416,7 +416,7 @@ function getDealRowObject(deal) {
},
modified: {
label: dateFormat(deal.modified, dateTooltipFormat),
timeAgo: timeAgo(deal.modified),
timeAgo: __(timeAgo(deal.modified)),
},
}
}
@ -437,44 +437,44 @@ function getContactRowObject(contact) {
},
modified: {
label: dateFormat(contact.modified, dateTooltipFormat),
timeAgo: timeAgo(contact.modified),
timeAgo: __(timeAgo(contact.modified)),
},
}
}
const dealColumns = [
{
label: 'Organization',
label: __('Organization'),
key: 'organization',
width: '11rem',
},
{
label: 'Amount',
label: __('Amount'),
key: 'annual_revenue',
width: '9rem',
},
{
label: 'Status',
label: __('Status'),
key: 'status',
width: '10rem',
},
{
label: 'Email',
label: __('Email'),
key: 'email',
width: '12rem',
},
{
label: 'Mobile no',
label: __('Mobile no'),
key: 'mobile_no',
width: '11rem',
},
{
label: 'Deal owner',
label: __('Deal owner'),
key: 'deal_owner',
width: '10rem',
},
{
label: 'Last modified',
label: __('Last modified'),
key: 'modified',
width: '8rem',
},
@ -482,27 +482,27 @@ const dealColumns = [
const contactColumns = [
{
label: 'Name',
label: __('Name'),
key: 'full_name',
width: '17rem',
},
{
label: 'Email',
label: __('Email'),
key: 'email',
width: '12rem',
},
{
label: 'Phone',
label: __('Phone'),
key: 'mobile_no',
width: '12rem',
},
{
label: 'Organization',
label: __('Organization'),
key: 'company_name',
width: '12rem',
},
{
label: 'Last modified',
label: __('Last modified'),
key: 'modified',
width: '8rem',
},

View File

@ -10,7 +10,7 @@
/>
<Button
variant="solid"
label="Create"
:label="__('Create')"
@click="showOrganizationModal = true"
>
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
@ -51,8 +51,8 @@
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
>
<OrganizationsIcon class="h-10 w-10" />
<span>No Organizations Found</span>
<Button label="Create" @click="showOrganizationModal = true">
<span>{{ __('No Organizations Found') }}</span>
<Button :label="__('Create')" @click="showOrganizationModal = true">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</div>
@ -88,10 +88,10 @@ const currentOrganization = computed(() => {
})
const breadcrumbs = computed(() => {
let items = [{ label: 'Organizations', route: { name: 'Organizations' } }]
let items = [{ label: __('Organizations'), route: { name: 'Organizations' } }]
if (!currentOrganization.value) return items
items.push({
label: currentOrganization.value.name,
label: __(currentOrganization.value.name),
route: {
name: 'Organization',
params: { organizationId: currentOrganization.value.name },
@ -126,7 +126,7 @@ const rows = computed(() => {
} else if (['modified', 'creation'].includes(row)) {
_rows[row] = {
label: dateFormat(organization[row], dateTooltipFormat),
timeAgo: timeAgo(organization[row]),
timeAgo: __(timeAgo(organization[row])),
}
}
})

View File

@ -8,7 +8,7 @@
v-if="tasksListView?.customListActions"
:actions="tasksListView.customListActions"
/>
<Button variant="solid" label="Create" @click="createTask">
<Button variant="solid" :label="__('Create')" @click="createTask">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</template>
@ -45,7 +45,10 @@
class="flex flex-col items-center gap-3 text-xl font-medium text-gray-500"
>
<EmailIcon class="h-10 w-10" />
<span>No Tasks Found</span>
<span>{{ __('No Tasks Found') }}</span>
<Button :label="__('Create')" @click="showTaskModal = true">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</div>
</div>
<TaskModal v-model="showTaskModal" v-model:reloadTasks="tasks" :task="task" />
@ -63,7 +66,7 @@ import { dateFormat, dateTooltipFormat, timeAgo } from '@/utils'
import { Breadcrumbs } from 'frappe-ui'
import { computed, ref } from 'vue'
const breadcrumbs = [{ label: 'Tasks', route: { name: 'Tasks' } }]
const breadcrumbs = [{ label: __('Tasks'), route: { name: 'Tasks' } }]
const { getUser } = usersStore()
@ -86,7 +89,7 @@ const rows = computed(() => {
if (['modified', 'creation'].includes(row)) {
_rows[row] = {
label: dateFormat(task[row], dateTooltipFormat),
timeAgo: timeAgo(task[row]),
timeAgo: __(timeAgo(task[row])),
}
} else if (row == 'assigned_to') {
_rows[row] = {

View File

@ -0,0 +1,47 @@
import { createResource } from 'frappe-ui'
export default function translationPlugin(app) {
app.config.globalProperties.__ = translate
window.__ = translate
if (!window.translatedMessages) fetchTranslations()
}
function format(message, replace) {
return message.replace(/{(\d+)}/g, function (match, number) {
return typeof replace[number] != 'undefined' ? replace[number] : match
})
}
function translate(message, replace, context = null) {
let translatedMessages = window.translatedMessages || {}
let translatedMessage = ''
if (context) {
let key = `${message}:${context}`
if (translatedMessages[key]) {
translatedMessage = translatedMessages[key]
}
}
if (!translatedMessage) {
translatedMessage = translatedMessages[message] || message
}
const hasPlaceholders = /{\d+}/.test(message)
if (!hasPlaceholders) {
return translatedMessage
}
return format(translatedMessage, replace)
}
function fetchTranslations(lang) {
createResource({
url: 'crm.api.get_translations',
cache: 'translations',
auto: true,
transform: (data) => {
window.translatedMessages = data
},
})
}