Have you ever thought of sending translated emails in any other language? If yes, so you are on the right page. Let’s understand and see how we can send emails translated into any other foreign language using LWC. This knowledge is impactful while sending emails to foreign clients. Here is an example of how to implement this in LWC:-
We will develop the following features:
● Send Email.
● Reset input fields on button click.
● Allow users to attach files and links.
● Translate subject and body according to ISO 639-1 Code for language. Ex: fr(French), hi(Hindi).
Here are the steps we will walk through to achieve the above functionalities:
● Subscribe to a translation API.
This will help us get the access key for the translation API.
● Add the named credential to your org.
This will authorize the endpoint in the org, hence making access to the endpoint.
● Create a class: EmailHandler
Here we will write methods to handle the REST API, searches on input email addresses, and translation of Input.
● Create LWC components:
emailInput - This will be the child component for emailLWC that will be handling input validation providing a toast message on invalid input, and reset input on click
emailLWC - This will set the translated text, and provide the functionality to attach files, links, and send emails.
Our output would come up like this:
The subject and body will be translated into the target language when clicking the translate button.
Step 1: Subscribe to a translation API
We will be using the powerful Google Translate API to translate text.
● Subscribe to google Translate API.
● Click on POST translate. The attributes on the right side would be used in translation in EmailHandler class. ( In step 3, you need to replace the ‘X-RapidAPI-key’ with your API key).
Step 2: Add a named credential for the Endpoint.
Create a named credential to specify the URL of a callout endpoint and its required authentication parameters in one definition. To know, how to add a named credential, You may refer to here.
Step 3: Make a class: EmailHandler
public with sharing class EmailHandler {
@AuraEnabled
public static void sendEmailController(String emailDetailStr) {
EmailWrapper emailDetails = (EmailWrapper)JSON.deserialize(
emailDetailStr,
EmailWrapper.class
);
Messaging.reserveSingleEmailCapacity(1);
try {
messaging.SingleEmailMessage mail = new messaging.SingleEmailMessage();
mail.setToAddresses(emailDetails.toAddress);
mail.setCcAddresses(emailDetails.ccAddress);
mail.setReplyTo(label.EMAIL_REPLY_ADDRESS);
mail.setSenderDisplayName(label.EMAIL_SENDER_NAME);
mail.setSubject(emailDetails.subject);
mail.setHtmlBody(emailDetails.body);
mail.setEntityAttachments(emailDetails.files);
Messaging.sendEmail(new List<messaging.SingleEmailMessage>{ mail });
} catch (exception e) {
throw new AuraHandledException(e.getMessage());
}
}
@AuraEnabled(cacheable = true)
public static String languageTranslate(String Text, String sourceLangCode, String targetLangCode) {
Http http = new http();
Httprequest req = new httprequest();
req.setEndpoint(label.TRANSLATIONAPI_ENDPOINT);
req.setMethod(label.API_METHOD_POST);
req.setHeader(label.CONTENT_TYPE, label.CONTENT_TYPE_VALUE);
req.setHeader(label.ACCEPT_ENCODING, label.ACCEPTED_ENCODING_VALUE);
req.setHeader(label.X_RAPIDAPI_KEY, ‘YOUR_RAPIDAPI_KEY_VALUE’);
req.setHeader('X-RapidAPI-Host', 'google-translate1.p.rapidapi.com');
String body = 'q=' + Text + '&target=' + targetLangCode + '&source=' + sourceLangCode;
req.setBody(body);
Httpresponse response = http.send(req);
Map<String,Object> data = (Map<String,Object>)((Map<String,Object>)JSON.deserializeUntyped(
response.getBody())
).get('data');
object translations = (object)data.get('translations');
String ans = JSON.serialize(translations).split(':')[1].substringBetween('"');
return ans;
}
Class EmailWrapper {
public List<String> toAddress;
public List<String> ccAddress;
public String subject;
public String body;
public List<String> files;
}
}
Step 4: Create LWC component: emailInput
emailInput.html
<template>
<div class="slds-combobox_container">
<div class={boxClass}>
<div class="slds-combobox__form-element slds-has-focus">
<template for:each={selectedValues} for:item="selectedValue" for:index="index">
<lightning-pill
key={selectedValue}
label={selectedValue}
onremove={handleRemove}
data-index={index}>
</lightning-pill>
</template>
<input
type="text"
id="to"
class="input"
required
onkeypress={handleKeyPress}
onblur={handleBlur}/>
</div>
<div
id="listbox-id-3"
class="slds-dropdown slds-dropdown_length-with-icon-7 slds-dropdown_fluid"
role="listbox"
if:true={hasItems}>
<ul class="slds-listbox slds-listbox_vertical" role="presentation">
<template for:each={items} for:item="item" for:index="index">
<li
key={item.Id}
data-id={item.Id}
onclick={onSelect}
role="presentation"
class="slds-listbox__item">
<div
aria-selected="true"
class=
"slds-media
slds-listbox__option slds-listbox__option_entity"
role="option"
tabindex="0">
<span class="slds-media__figure slds-listbox__option-icon">
<span class="slds-icon_container slds-icon-standard-account">
<svg class="slds-icon slds-icon_small" aria-hidden="true">
<use
xlink:href= SVG_ACCOUNT_LINK>
</use>
</svg>
</span>
</span>
<span class="slds-media__body">
<span class="slds-listbox__option-text slds-listbox__option-text_entity">
{item.Name}</span>
<span class="slds-listbox__option-meta slds-listbox__option-meta_entity">
{item.Email}</span>
</span>
</div>
</li>
</template>
</ul>
</div>
</div>
</div>
</template>
emailInput.js
import { LightningElement, track, api } from "lwc";
const ENTER_KEY_CODE = 13;
const BLUR_TIMEOUT_VALUE = 300;
const SVG_ACCOUNT_LINK = "/assets/icons/standard-sprite/svg/symbols.svg#account";
export default class EmailInput extends LightningElement {
@track items = [];
searchTerm = "";
blurTimeout;
boxClass = "slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-has-focus";
_selectedValues = [];
selectedValuesMap = new Map();
get selectedValues() {
return this._selectedValues;
}
set selectedValues(value) {
this._selectedValues = value;
const selectedValuesEvent = new CustomEvent(
"selection", {detail: {selectedValues: this._selectedValues}
});
this.dispatchEvent(selectedValuesEvent);
}
get hasItems() {
return this.items.length;
}
handleBlur() {
this.blurTimeout = setTimeout(() => {
this.boxClass = "slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-has-focus";
const value = this.template.querySelector('input.input').value
if (value !== undefined && value != null && value !== "") {
this.selectedValuesMap.set(value, value);
this.selectedValues = [...this.selectedValuesMap.keys()];
}
this.template.querySelector('input.input').value = "";
}, BLUR_TIMEOUT_VALUE);
}
handleKeyPress(event) {
if (event.keyCode === ENTER_KEY_CODE) {
event.preventDefault();
const value = this.template.querySelector('input.input').value;
if (value !== undefined && value != null && value !== "") {
this.selectedValuesMap.set(value, value);
this.selectedValues = [...this.selectedValuesMap.keys()];
}
this.template.querySelector('input.input').value = "";
}
}
handleRemove(event) {
const item = event.target.label;
this.selectedValuesMap.delete(item);
this.selectedValues = [...this.selectedValuesMap.keys()];
}
onSelect(event) {
this.template.querySelector('input.input').value = "";
let ele = event.currentTarget;
let selectedId = ele.dataset.id;
let selectedValue = this.items.find((record) => record.Id === selectedId);
this.selectedValuesMap.set(selectedValue.Email, selectedValue.Name);
this.selectedValues = [...this.selectedValuesMap.keys()];
let key = this.uniqueKey;
const valueSelectedEvent = new CustomEvent("valueselect", {
detail: { selectedId, key }
});
this.dispatchEvent(valueSelectedEvent);
if (this.blurTimeout) {
clearTimeout(this.blurTimeout);
}
this.boxClass = "slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-has-focus";
}
@api reset() {
this.selectedValuesMap = new Map();
this.selectedValues = [];
}
@api validate() {
this.template.querySelector('input').reportValidity();
const isValid = this.template.querySelector('input').checkValidity();
return isValid;
}
}
Step 5: Create LWC component: emailLWC
emailLWC.html
<template>
<article class="slds-card">
<!-- Alert -->
<div if:true={noEmailError} class="slds-notify slds-notify_alert slds-alert_error" role="alert">
<span class="slds-assistive-text">{label.ERROR}</span>
<span
class="slds-icon_container slds-icon-utility-error slds-m-right_x-small"
title="Description of icon when needed">
<svg class="slds-icon slds-icon_x-small" aria-hidden="true">
<use xlink:href={SVG_ERROR_LINK}></use>
</svg>
</span>
<h2>
{label.RECEIPENT}
</h2>
<div class="slds-notify__close">
<button
class="slds-button slds-button_icon slds-button_icon-small slds-button_icon-inverse"
title="Close">
<svg class="slds-button__icon" aria-hidden="true">
<use xlink:href={SVG_CLOSE_LINK}></use>
</svg>
<span class="slds-assistive-text">{label.CLOSE}</span>
</button>
</div>
</div>
<div if:true={invalidEmails} class="slds-notify slds-notify_alert slds-alert_error" role="alert">
<span class="slds-assistive-text">{label.ERROR}</span>
<span
class="slds-icon_container slds-icon-utility-error slds-m-right_x-small"
title="Description of icon when needed">
<svg class="slds-icon slds-icon_x-small" aria-hidden="true">
<use xlink:href={SVG_ERROR_LINK}></use>
</svg>
</span>
<h2>
{label.INVALID_EMAIL_MESSAGE}
</h2>
<div class="slds-notify__close">
<button
class="slds-button slds-button_icon slds-button_icon-small slds-button_icon-inverse"
title="Close">
<svg class="slds-button__icon" aria-hidden="true">
<use xlink:href=>{SVG_CLOSE_LINK}</use>
</svg>
<span class="slds-assistive-text">{label.CLOSE}</span>
</button>
</div>
</div>
<div class="slds-card__body slds-card__body_inner">
<div class="slds-form form slds-var-p-top_small">
<div class="slds-form-element slds-form-element_horizontal slds-form-element_1-col">
<label class="slds-form-element__label" for="to">{label.TO}</label>
<div class="slds-form-element__control">
<c-email-input onselection={handleToAddressChange}></c-email-input>
</div>
</div>
<div class="slds-form-element slds-form-element_horizontal slds-form-element_1-col">
<label class="slds-form-element__label" for="cc">{label.CC}</label>
<div class="slds-form-element__control">
<c-email-input onselection={handleCcAddressChange}></c-email-input>
</div>
</div>
<div class="slds-form-element">
<label class="slds-form-element__label" for="sourceLangCode">{label.SOURCE_LANGUAGE_CODE}
</label>
<div class="slds-form-element__control">
<input
type="text"
name={label.SOURCE_LANGUAGE_CODE}
id="sourceLangCode"
value={sourceLangCode}
class="slds-input"
onchange={handleSourceChange}/>
</div>
</div>
<div class="slds-form-element">
<label class="slds-form-element__label" for="targetLangCode">{label.TARGET_LANGUAGE_CODE}
</label>
<div class="slds-form-element__control">
<input
type="text"
name={label.TARGET_LANGUAGE_CODE}
id="targetLangCode"
value={targetLangCode}
class="slds-input"
onchange={handleTargetChange}/>
</div>
</div>
<div class="slds-form-element">
<label class="slds-form-element__label" for="subject"> </label>
<div class="slds-form-element__control">
<input
type="text"
name={label.SUBJECT}
id="subject"
value={subject}
placeholder={label.SUBJECT}
class="slds-input"
onchange={handleSubjectChange}/>
</div>
</div>
<div class="slds-form-element">
<div class="slds-form-element__control slds-var-p-top_small">
<lightning-input-rich-text value={body} onchange={handleBodyChange}> </lightning-input-rich-text>
</div>
</div>
</div>
</div>
<div class="slds-var-p-around_medium">
<template for:each={files} for:item="file" for:index="index">
<lightning-pill
key={file.contentVersionId}
label={file.name}
onremove={handleRemove}
data-id={file.contentVersionId}
data-index={index}>
<lightning-icon
icon-name="doctype:attachment"
size="xx-small"
alternative-text={label.ATTACH_FILES}>
</lightning-icon>
</lightning-pill>
</template>
</div>
<div class="slds-grid slds-grid_align-end slds-var-p-around_x-small">
<div class="slds-col slds-var-p-right_x-small slds-var-p-bottom_x-small slds-is-relative">
<section
if:true={wantToUploadFile}
aria-describedby="dialog-body-id-108"
aria-labelledby="dialog-heading-id-3"
class="slds-popover slds-popover_walkthrough slds-nubbin_bottom slds-is-absolute popover"
role="dialog">
<button class=
"slds-button slds-button_icon slds-button_icon-small
slds-float_right
slds-popover__close
slds-button_icon-inverse"
title="Close dialog">
<lightning-button-icon
variant="bare-inverse"
size="small"
onclick={toggleFileUpload}
icon-name="utility:close"
alternative-text="close">
</lightning-button-icon>
<span class="slds-assistive-text">{label.CLOSE}</span>
</button>
<header class="slds-popover__header slds-p-vertical_medium">
<h2 id="dialog-heading-id-3" class="slds-text-heading_medium">{label.UPLOAD_FILES} </h2>
</header>
<div class="slds-popover__body" id="dialog-body-id-108">
<lightning-file-upload
label={label.ATTACH_FILES}
name={label.ATTACH_FILES}
accept={acceptedFormats}
record-id={myRecordId}
onuploadfinished={handleUploadFinished}
multiple>
</lightning-file-upload>
</div>
</section>
<lightning-button-icon
icon-name="utility:attach"
onclick={toggleFileUpload}
alternative-text={label.ATTACH_FILES}
title={label.ATTACH_FILES}>
</lightning-button-icon>
</div>
<div class="slds-col slds-var-p-right_x-small slds-var-p-bottom_x-small">
<lightning-button label={label.RESET} title={label.RESET} onclick={handleReset}></lightning-button>
</div>
<div class="slds-col slds-var-p-right_x-small slds-var-p-bottom_x-small">
<lightning-button label={label.TRANSLATE} title={label.TRANSLATE} onclick={handleTranslation}>
</lightning-button>
</div>
<div class="slds-col slds-var-p-right_x-small slds-var-p-bottom_x-small">
<lightning-button
variant="brand"
label={label.SEND}
title={label.SEND}
onclick={handleSendEmail}>
</lightning-button>
</div>
</div>
</article>
</template>
emailLWC.js
import { LightningElement, track } from "lwc";
import sendEmailController from "@salesforce/apex/EmailHandler.sendEmailController";
import languageTranslate from "@salesforce/apex/EmailHandler.languageTranslate";
import ERROR from "@salesforce/label/c.ERROR";
import CLOSE from "@salesforce/label/c.CLOSE";
import RECEIPENT from "@salesforce/label/c.RECEIPENT";
import UPLOAD_FILES from "@salesforce/label/c.UPLOAD_FILES";
import INVALID_EMAIL_MESSAGE from "@salesforce/label/c.INVALID_EMAIL_MESSAGE";
import ATTACH_FILES from "@salesforce/label/c.ATTACH_FILES";
import TRANSLATE from "@salesforce/label/c.TRANSLATE";
import EMAIL_SENDER_NAME from "@salesforce/label/c.EMAIL_SENDER_NAME";
import EMAIL_REPLY_ADDRESS from "@salesforce/label/c.EMAIL_REPLY_ADDRESS";
import SOURCE_LANGUAGE_CODE from "@salesforce/label/c.SOURCE_LANGUAGE_CODE";
import TARGET_LANGUAGE_CODE from "@salesforce/label/c.TARGET_LANGUAGE_CODE";
import RAPIDAPI_KEY_VALUE from "@salesforce/label/c.RAPIDAPI_KEY_VALUE";
import X_RAPIDAPI_KEY from "@salesforce/label/c.X_RAPIDAPI_KEY";
import ACCEPTED_ENCODING_VALUE from "@salesforce/label/c.ACCEPTED_ENCODING_VALUE";
import ACCEPT_ENCODING from "@salesforce/label/c.ACCEPT_ENCODING";
import CONTENT_TYPE from "@salesforce/label/c.CONTENT_TYPE";
import CONTENT_TYPE_VALUE from "@salesforce/label/c.CONTENT_TYPE_VALUE";
import API_METHOD_POST from "@salesforce/label/c.API_METHOD_POST";
import TRANSLATIONAPI_ENDPOINT from "@salesforce/label/c.TRANSLATIONAPI_ENDPOOINT";
import SEND from "@salesforce/label/c.SEND";
import RESET from "@salesforce/label/c.RESET";
import TO from "@salesforce/label/c.TO";
import CC from "@salesforce/label/c.CC";
const SVG_ERROR_LINK = "/assets/icons/utility-sprite/svg/symbols.svg#error";
const SVG_CLOSE_LINK = "/assets/icons/utility-sprite/svg/symbols.svg#close";
export default class EmailLWC extends LightningElement {
toAddress = [];
ccAddress = [];
@track files = [];
sourceLangCode = "";
targetLangCode = "";
subject = "";
body = "";
error;
wantToUploadFile = false;
noError = false;
invalidEmails = false;
label = {ERROR, CLOSE, RECEIPENT, UPLOAD_FILES, INVALID_EMAIL_MESSAGE, TO, CC};
toggleFileUpload() {
this.wantToUploadFile = !this.wantToUploadFile;
}
handleUploadFinished(event) {
const uploadedFiles = event.detail.files;
this.files = [...this.files, ...uploadedFiles];
this.wantToUploadFile = false;
}
handleRemove(event) {
const index = event.target.dataset.index;
this.files.splice(index, 1);
}
handleToAddressChange(event) {
this.toAddress = event.detail.selectedValues;
}
handleCcAddressChange(event) {
this.ccAddress = event.detail.selectedValues;
}
handleSourceChange(event) {
this.sourceLangCode = event.target.value;
}
handleTargetChange(event) {
this.targetLangCode = event.target.value;
}
handleSubjectChange(event) {
this.subject = event.target.value;
}
handleBodyChange(event) {
this.body = event.target.value;
}
handleTranslation() {
languageTranslate({
Text: this.subject,
sourceLangCode: this.sourceLangCode,
targetLangCode: this.targetLangCode
})
.then(result => {
this.subject = result;
})
.catch((error) => {
this.error = error;
});
languageTranslate({
Text: this.body,
sourceLangCode: this.sourceLangCode,
targetLangCode: this.targetLangCode
})
.then(result => {
this.body = result;
})
.catch((error) => {
this.error = error;
});
}
validateEmails(emailAddressList) {
let areEmailsValid;
if(emailAddressList.length > 1) {
areEmailsValid = emailAddressList.reduce((accumulator, next) => {
const isValid = this.validateEmail(next);
return accumulator && isValid;
});
}
else if(emailAddressList.length > 0) {
areEmailsValid = this.validateEmail(emailAddressList[0]);
}
return areEmailsValid;
}
validateEmail(email) {
const res =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()s[\]\\.,;:\s@"]+)*)|
(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|
(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return res.test(String(email).toLowerCase());
}
handleReset() {
this.toAddress = [];
this.ccAddress = [];
this.subject = "";
this.body = "";
this.files = [];
this.sourceLangCode = "";
this.targetLangCode = "";
this.template.querySelectorAll("c-email-input").forEach((input) => input.reset());
}
handleSendEmail() {
this.noError = false;
this.invalidEmails = false;
if (![...this.toAddress, ...this.ccAddress].length > 0) {
this.noError = true;
return;
}
if (!this.validateEmails([...this.toAddress, ...this.ccAddress])) {
this.invalidEmails = true;
return;
}
let emailDetails = {
toAddress: this.toAddress,
ccAddress: this.ccAddress,
subject: this.subject,
body: this.body
};
sendEmailController({emailDetailStr: JSON.stringify(emailDetails)})
.then(() => {
})
.catch((error) => {
this.error = error;
});
}
}
emailLWC.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__RecordPage</target>
</targets>
</LightningComponentBundle>
CUSTOM LABELS USED:
{label.TO} | To |
{label.CC} | Cc |
{label.CLOSE} | Close |
{label.ERROR} | Error |
{label.SUBJECT} | Subject |
{label.RECEIPENT} | Please add a receipent |
{label.ATTACH_FILES} | Attach files |
{label.TRANSLATE} | Translate |
{label.UPLOAD_FILES} | Upload Files |
{label.INVALID_EMAIL_MESSAGE} | Some of the emails are invalid |
{label.SOURCE_LANGUAGE_CODE} | Source Language Code |
{label.TARGET_LANGUAGE_CODE} | Target Language Code |
{label.EMAIL_REPLY_ADDRESS} | team.avenoir@gmail.com |
{label.EMAIL_SENDER_NAME} | Avenoir Innovations Pvt Ltd. |
{label.TRANSLATIONAPI_ENDPOINT} | https://google-translate1.p.rapidapi.com/language/translate/v2 |
{label.API_METHOD_POST} | POST |
{label.CONTENT_TYPE} | content-type |
{label.CONTENT_TYPE_VALUE} | application/x-www-form-urlencoded |
{label.ACCEPT_ENCODING} | Accept-Encoding |
{label.ACCEPTED_ENCODING_VALUE} | application/gzip |
{label.X_RAPIDAPI_KEY} | X-RapidAPI-Key |
{label.RAPIDAPI_KEY_VALUE} | your API key |
{label.SEND} | Send |
{label.RESET} | Reset |
| |
Conclusion
In conclusion, this tutorial provides a comprehensive guide on how to create a Lightning Web Component (LWC) for sending translated emails in foreign languages. It outlines a step-by-step process for implementing this feature, including setting up a translation API, creating a named credential, developing an LWC component, and handling email-sending functionality. The LWC allows users to input email recipients, subject, and body, translate the content into the target language, attach files, and send emails. This solution is particularly valuable for communicating with foreign clients. By following this guide, you can enhance your Salesforce org's email capabilities, making it more accessible and efficient for global communication.
If you'd like to see the code and resources used in this project, you can access the repository on GitHub.To access the AVENOIRBLOGS repository, click here. Feel free to explore the code and use it as a reference for your own projects.
Happy Coding!! To help you add to your knowledge. You can leave a comment to help me understand how the blog helped you. If you need further assistance, please leave a comment or contact us at Reach us. You can click on "Reach Us" on the website and share the issue with me.
REFERENCES:
Blog Credit:
D. Dewangan
Salesforce Developer
Avenoir Technologies Pvt. Ltd.
Reach us: team@avenoir.ai
Comentarios