Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
javieryw
Associate
Associate

Scenario Overview

 At SAP Japan, diverse teams face communication challenges due to language barriers, hindering collaboration and innovation. We revived a forgotten language exchange app, empowering it with AI to revolutionize internal communication. Our app proactively addresses language barriers in talent acquisition, customer engagement, and networking events.

This blog will dive deeper into how this BTP & AI-powered app simplifies language learning, why multilingual support is crucial in a global organization, and how it fosters global connection and collaboration. Witness the journey of AI transforming communication at SAP Japan, one conversation at a time.

 

Solution Overview

The diagram provided below illustrates a summary of the business process of the application:

ss1.png

Business Process Diagram

 

The diagram provided below illustrates a summary of the application configuration and the flow of requests between components:

ss2.png

Architecture Diagram

 

Technical Setup

CAP Application

The application consists of creating multiple services which are being called from SAP Build. The code below shows the CAP application schema definition

 

 

using {managed} from '@sap/cds/common';
namespace sap.capire.languageexchange;

entity User {
    key email : String(255) not null;
    firstName : String(100) not null;
    lastName: String (100) not null;
    description: LargeString;
    summary: LargeString;
    canTeach: LargeString;
    wantToLearn: LargeString;
    overseasPartner: String(10);
    country: String (100);
    imageData: LargeBinary @Core.MediaType: imageType;
    imageType: String @Core.IsMediaType;
    languagesToLearn: many String(255);
    languagesToTeach: many String(255);
    latitude: Decimal(9,6); // GPS Latitude
    longitude: Decimal(9,6); // GPS Longitude
    messages: Association to many Message on messages.receiverEmail = $self;
    favorites: Association to many Favorite on favorites.favoriteUserEmail = email;
}
entity Message {
    key ID: UUID;
    senderEmail: Association to User on senderEmail.email = sender_email  ;
    sender_email: type of User:email;
    receiverEmail : Association to User on receiverEmail.email = receiver_email  ;
    receiver_email: type of User:email;
    content : LargeString;
    status : String(20);
}

entity Favorite {
    key userEmail: String(255) not null;
    key favoriteUserEmail :String(255) not null ;
}

entity Languages {
    key ID: Integer;
    ISOCODE: String(2);
    SPRSL: String(2);
    SPTXT: String(200);
    SPTXT2: String(200);
}

entity Countries {
    key ID: Integer;
    ISOCODE: String(2);
    LAND1: String(2);
    LANDX: String(200);
    NATIO:String(200);
    LANDX50: String(200);
    NATIO50: String(200);
}

 

 

The code below shows the code to expose the services using the services.cds file

 

 

using {sap.capire.languageexchange as languageexchange} from '../db/schema';

@path:'service/languageexchange'
service LanguageExchange{
    entity User as projection on languageexchange.User;

    entity Message as projection on languageexchange.Message;

    entity Favorite as projection on languageexchange.Favorite;

    entity Languages as projection on languageexchange.Languages; 

    entity Countries as projection on languageexchange.Countries;
}

 

 

Loaded initial data via csv file that is exported from our on-premise S/4 HANA master data into both the Countries and Languages table so that we can dynamically set the language of the application and country text based on the output given by OpenAI using the Resume Uploader Flask API.

ss3.png

Initial data for countries table

ss4.pngInitial data for languages table

 

For integration with SAP Build Apps we also had to account for the CORS policies

 

 

"use strict";

const cds = require("@sap/cds");
const cors = require("cors");
cds.on("bootstrap", app => app.use(cors()));

module.exports = cds.server;

 

 

With all the backend services in place, I then proceeded to deploy the CAP application to a HANAHDI Container hosted on Kyma with the help of Helm, Pack, and Docker. Details on how to perform this process can be found in this tutorial.

Then I proceed to integrate the CAP Application into Build Apps simply by establishing an ODataIntegration. Details on how to perform this process can be found in this tutorial.

 

Technical Highlights

Establishing Multilingual Support for Application

Please refer to the logic canvas below to see how multilingual support is established in SAP Build Apps

ss5.png

Overview of the Logic Canvas

The custom javascript function getLocale will get the current locale from the user's browser

ss6.png

 getLocale Function

 

The custom javascript function Return Language based on Browser Locale and Return Countries based on Browser Locale will filter from the languages and countries table and return the list of languages and countries based on the user's browser locale

ss7.png

Return Languages based on Browser Locale Function

 

In SAP Build Apps we set the supported languages, fallback languages, and any other in-app text manually

ss7.png

 

 

Suggested Users Custom Javascript Function on SAP Build Apps

The list of users can be retrieved using the Get record flow function and a list of objects will be returned in JSON format.

ss9.png

Overview of Logic Canvas

 

Based on the output of the Get record flow function, filter by the user's preferences such as whether he/she is willing to meet an overseas partner, followed by the type of languages that the users can speak and lastly sort them by alphabetical order.

 

 

const currentLoggedInUser = inputs.input1;
const odataURL = "https://languageexchange-srv-language-exchange.c-290ae5b.kyma.shoot.live.k8s-hana.ondemand.com/service/languageexchange/User";

var output_ = {};

// Make a GET request to fetch user records
await fetch(odataURL)
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    // Filter the user records based on selected criteria
    const current_user_languages_to_learn = currentLoggedInUser.languagesToLearn;
    const isOverseasPartner = currentLoggedInUser.overseasPartner === "Yes";

    // Filter users based on language preferences, overseasPartner field, and country
    const allLanguagesMatchedUsers = data.value.filter(user => {
        const userIsInSameCountry = user.country === currentLoggedInUser.country;
        const userIsInDifferentCountry = user.country !== currentLoggedInUser.country;
        const userHasOverseasPartner = user.overseasPartner === "Yes";

        if ((userHasOverseasPartner && (isOverseasPartner && !userIsInSameCountry)) || (!userHasOverseasPartner && (!isOverseasPartner && userIsInDifferentCountry))) {
            return current_user_languages_to_learn.every(language => user.languagesToTeach.includes(language));
        } else {
            return false;
        }
    });

    const someLanguagesMatchedUsers = data.value.filter(user => {
        const userIsInSameCountry = user.country === currentLoggedInUser.country;
        const userIsInDifferentCountry = user.country !== currentLoggedInUser.country;
        const userHasOverseasPartner = user.overseasPartner === "Yes";

        if ((userHasOverseasPartner && (isOverseasPartner && !userIsInSameCountry)) || (!userHasOverseasPartner && (!isOverseasPartner && userIsInDifferentCountry))) {
            return current_user_languages_to_learn.some(language => user.languagesToTeach.includes(language)) && !allLanguagesMatchedUsers.includes(user);
        } else {
            return false;
        }
    });

    // Sort users alphabetically by first name
    allLanguagesMatchedUsers.sort((a, b) => a.firstName.localeCompare(b.firstName));
    someLanguagesMatchedUsers.sort((a, b) => a.firstName.localeCompare(b.firstName));

    // Concatenate the sorted arrays to get the final result
    const matching_users = allLanguagesMatchedUsers.concat(someLanguagesMatchedUsers);

    // Log or use the sorted matching_users array as needed

    // Log the filtered user records
    console.log(matching_users);
    output_ = matching_users;
  })
  .catch(error => {
    console.error("Error fetching or processing data:", error);
  });

return { result: output_ };

 

 

 

Near You Custom Javascript Function on SAP Build Apps

We use the GPS location flow function to retrieve the current latitude and longitude from the user's device.

ss10.png

Overview of Logic Canvas

Based on the latitude and longitude that have been retrieved, I created a custom javascript function that used the Haversine formula to filter the list of users within 1km - 1000km.

 

 

const excludedUser = inputs.currentEmail;
const odataURL = "https://languageexchange-srv-language-exchange.c-290ae5b.kyma.shoot.live.k8s-hana.ondemand.com/service/languageexchange/User";
const lat = inputs.lat;
const long = inputs.long;
const distanceThreshold = inputs.distance; // in meters
console.log(distanceThreshold);

output_ = {};

// Make a GET request to fetch user records
await fetch(odataURL)
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    // Filter the user records based on selected criteria
    const nearbyUsers = data.value.filter(user => {
      const shouldExcludeUser = user.email === excludedUser;
      if (shouldExcludeUser) {
        return false;
      }
      // Calculate distance between two points using Haversine formula
      const userLat = user.latitude;
      const userLong = user.longitude;
      const radlat1 = Math.PI * lat / 180;
      const radlat2 = Math.PI * userLat / 180;
      const theta = long - userLong;
      const radtheta = Math.PI * theta / 180;
      let distance = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
      distance = Math.acos(distance);
      distance = distance * 180 / Math.PI;
      distance = distance * 60 * 1.1515 * 1.609344 * 1000; // in meters
      
      return distance <= distanceThreshold;
    });
    console.log(nearbyUsers);
    nearbyUsers.sort((a, b) => {
        // Compare first names
        let firstNameComparison = a.firstName.localeCompare(b.firstName);
        
        // If first names are the same, compare last names
        if (firstNameComparison === 0) {
            return a.lastName.localeCompare(b.lastName);
        }
        
        return firstNameComparison;
    });
    
    // Log the filtered user records
    output_ = nearbyUsers;
  })
  .catch(error => {
    console.error("Error fetching or processing data:", error);
  });

return { result: output_ };

 

 

Video Example

You may view the demo of the application here

Conclusions

Leveraging the SAP BTP platform's robust capabilities, I seamlessly integrated and facilitated communication between various applications. Guided by comprehensive tutorials, I delved into CAP development, gaining practical experience within a week. This enabled me to confidently add, edit, and create services and functions within the CAP application, ultimately deploying it to Cloud Foundry with ease.

Beyond the core platform, the integration with openAI provided valuable insights into their functionalities. This enriched my understanding of the underlying mechanisms and solidified my technical foundation.

I trust you found this article informative. Feel free to share your thoughts or ask any questions in the SAP Build Question section. I am always happy to engage in further discussions.