I'm working with the BotFramework and I'm seeking to customize the alignment of messages rendered in my chat interface. Specifically, I want to ensure that messages in Arabic are right-aligned while messages in English remain left-aligned.
Currently, I'm using a C# SDK and WebChat for rendering the chat interface. However, I couldn't find a straightforward way to dynamically adjust the alignment based on the language of the message. Can anyone guide me on how to achieve this customization?
Any insights, code snippets, or pointers to relevant documentation would be greatly appreciated! Chat example
I've tried using Activity.TextFormat xml, markdown, but nothing worked for me.
While Web Chat does support 'rtl' formatting of text, it does so for the entire client. Unfortunately, there isn't a mechanism that allows doing this on a message-by-message basis. But this is achievable with a little work.
First, we need to setup the bot so it knows when to send an activity using a particular language. For instance, you could use a localization library, like I18N, to detect the language and send an appropriate response. However, for simplicities sake, I am cheating by detecting a 'send arabic' command to then send an activity.
Please be aware, I am doing this using the JS SDK (as it's the language I know), but the logic is simple enough to follow. Also, please accept any apologies regarding the translated text. I merely grabbed some text off of a page, used a translator, and plopped it in.
As for the code, you'll notice that I have commented out the locale
property and, instead, included it in the channelData
property. This is because the locale
property cannot be overridden in an activity. The locale is determined by the Azure Bot Service and can only be changed, for instance, when a DirectLine client, such as Web Chat, is generated and connects to the service. However, since we are working message-by-message, setting the locale for the whole client doesn't serve us in this scenario which is why I specify the locale in the channelData
property.
In your bot:
if (activity.text === 'send arabic') {
await context.sendActivity(
{
text: 'هل أنت بصدد إنشاء واجهة مستخدم رسومية أمامية لبرنامج PowerShell النصي الخاص بك؟ هل اصطدمت بجدار مع تجميد واجهة المستخدم الرسومية Winforms؟ هل تواجه مشكلة في التخلص من تجمعات Runspace وجداول التجزئة وجلسات الولاية وتنظيف القمامة؟ \r\n أو ربما تريد ببساطة نقل لعبة البرمجة النصية إلى المستوى التالي ، وإنشاء أكثر من مجرد أمر cmdlet قديم عادي يعمل في نافذة وحدة التحكم. اقتحم مشهد مطور AAA هذا باستخدام تطبيق مرن ... حسنا ، ليس تماما ، لكننا نصل إلى هناك! \r\n ثم هذا بلوق وظيفة الحق في زقاقك...',
type: 'message',
// locale: 'ar',
channelData: { locale: 'ar' },
},
);
}
Next, we need to setup Web Chat to detect the incoming activity from the bot so it knows when to display the text using 'rtl' formatting. For this, we use Web Chat's store
. In the store, we monitor for an activity that includes text as well as the channelData
property that also includes the locale
property with a value of "ar".
Once it is detected, we then search for the associated HTML elements being rendered. We do this by looking for the classes used when rendering text. And we use .querySelectorAll()
to create an array of the found elements. As there isn't anything we can use to specify the exact text elements, we simply use the generated array and select the last element within it. We then find the closest element that contains the webchat__bubble
class as this is the one that controls the text direction, and add the webchat__bubble--rtl
class and the dir
attribute with a value of 'rtl'.
Lastly, we need to wrap all of that in a setTimeout()
. Why? Because of how Web Chat functions. All incoming activities are passed thru its store
where they are processed before being rendered. Without the setTimeout()
, any changes we try to impose on the DOM will occur before Web Chat has rendered the elements. So we set the timeout to somewhere between 100 - 400 ms. Less than 100 and we suffer the above problem. If we use a value greater than 400, we run the risk of any changes rendering visibly in front of the user.
In your Web Chat script:
const store = window.WebChat.createStore( {}, ( { dispatch } ) => next => async action => {
if ( action.type === 'DIRECT_LINE/INCOMING_ACTIVITY' ) {
const { activity } = action.payload;
if ( activity.text && activity.channelData && activity.channelData.locale === 'ar' ) {
setTimeout(() => {
const textElements = document.querySelectorAll('.webchat__bubble__content .webchat__text-content');
const elementIndex = textElements.length - 1;
// textElements[elementIndex].style.direction = 'rtl';
const bubbleFromUser = textElements[elementIndex].closest('.webchat__bubble');
bubbleFromUser.classList.add('webchat__bubble--rtl');
bubbleFromUser.setAttribute( 'dir', 'rtl' );
}, 300);
}
}
next( action );
} );
window.ReactDOM.render(
<ReactWebChat
directLine={ directLine }
store={ store }
activityMiddleware={ activityMiddleware }
/>,
document.getElementById( 'webchat' )
);
Now, a few things to note. You should consider this a workaround and an imperfect one, at that. But it is one I have used for several years and have also suggested to many others. I have yet to hear a complaint. But keep in mind that:
Hope of help!