Search code examples
c#botframeworkdirect-line-botframework

Customize BotFramework message alignment


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.


Solution

  • 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' )
    );
    

    enter image description here

    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:

    • We are making changes to the DOM. And, as Web Chat is built using React, Web Chat won't know we've made any changes. Consequently, if you are using local/sessionStorage to persist a conversation and the user refreshes or navigates away and comes back, any UI changes made will have been lost.
    • We are making changes based on class names and elements that may change in a future release of Web Chat. Should there be a change, then it will break your instance of Web Chat until you can make an update, yourself.
    • The timeout isn't foolproof. Again, I haven't heard or experienced any issues, but a delay in the network or at some other point could affect how well this works.

    Hope of help!