This blog is written by Arwold Koelewijn (sr SAP BI consultant, Tacstone) and Attila Houtkooper (sr Web Developer, Roughdot websolutions).


Introduction


This component is used to translate descriptions of elements and to display comments in the dashboards, based on a datasource as input.

As of DS1.4 standard functionality is available to do translating. However, this component will remain useful since:

  • The business users are in charge of translating. Changes and additions are easily uploaded into SAP BI;
  • Dashboard comments (up to 2000 characters) are loaded from a source system;

Initially we started of using Design Studio scripting to do the translating.

But since it can occur that several lines of 60 characteristics need to be compounded, it is more efficient to do this in a SDK component. Also, in our environment it turned out to be better for performance as well.


How does it work?

For example, the text:

‘There were sufficient opportunities (assignments, activities, interim exams) for my study progress to be tested during the course’

Is delivered by the datasource (DS_TAAL) as:

/wp-content/uploads/2015/02/plaatje1_642250.png

In the properties of the component, the language (EN) and de key (T142) is set.

/wp-content/uploads/2015/02/plaatje2_642404.png

The component uses the language (EN) and the key (T142) to retrieve the corresponding data set, and join the rows together in one string.

The dashboard comments are stored in a similar way.

The source systems delivers the comments in html format, example:

<strong>This course</strong> has been well attended and well reviewed.</br></br>Next year we will improve the course in the following way:<ul> <li>smaller groups in the tutorials</li> <li>new reading material</li></ul>  </br>Further will we…

In BW using start- and endroutines this string is cut into pieces of 60 characters.


Example of a dashboard using translated text and comments

Screenprint report.png



The component has been build by Attila Houtkooper, he will shed some light on the scripting of the component in this blog


Building the component


Well, there were a couple of considerations from the get-go.

The component needed to have a simple interface that allowed updates through scripting, such as setting the language automatically. It also needed to be fast; this component is used a lot throughout a single report, so there it can take up only a small footprint in terms of memory usage. Following the best practice of Don’t Repeat Yourself (DRY), the translation data is loaded only once, when the page loads.

To accommodate for this the interface of the component became quite simple, there are setters (and for no particular reason also getters) for the following properties: language, phrase (identifier) and data(source).

  • Language; denotes the internal key representing a language, this can be a locale such as “en_US” or “de_DE”, in our implementation simple two letter codes are used, namely “EN” and “NL”.
  • Phrase (identifier); this is the specific phrase identifier that is to be displayed, e.g. “T142”.
  • Data(source); this is where the datasource (DS_TAAL) goes that is described above.

When translations are larger than 60 characters, the string gets split up into blocks of 60 characters and within the component it is important to put them together again. The translations are aggregated as follows:

The component will want to look up a translation with a simple 2-dimensional map; the first level being the language and the second being the phrase identifier.

The most interesting logic is where the different parts of a phrase are concatenated together to form a single string. This logic is described in the JavaScript source below:

// It is assumed to be chopped up into chunks of max 60 characters each.
// These chunks are concatenated and stored for displaying.
customComponent_translations = {};
var dimensions = resultSet[“dimensions”];

resultSet[“axis_rows”].forEach(function(valueIndexRow) {
var languageIndex = valueIndexRow[0];
var languageId = dimensions[0][“members”][languageIndex][“text”];
var phraseIndex = valueIndexRow[1];
var phraseId = dimensions[1][“members”][phraseIndex][“text”];
var chunkIndex = valueIndexRow[3];
var chunkText = dimensions[3][“members”][chunkIndex][“text”];

       // If there are no translations aggregated yet for the language, add a new map.
if (customComponent_translations[languageId] === undefined) {
customComponent_translations[languageId] = {};
}

       // If the phrase (identifier) has not been encountered before, initialize it.
if (customComponent_translations[languageId][phraseId] === undefined) {
customComponent_translations[languageId][phraseId] = “”;
}

       customComponent_translations[languageId][phraseId] += chunkText;
});


Two elements of the data source are used; the axis_rows and the dimensions. Looping through the axis_rows, we assume that the data is ordered as follows: languageId, phraseId, chunkText. It is the chunkText that may only be a part of the entire text. Notice that while getting the positions of each of these values an index is skipped. Element 2 contains a serial number of the chunk. We assume the data to be ordered so we do not make use of the index.

Once all important values (languageId, phraseId and chunkText) have been identified, subsequently the language and the phraseId will be initialized if necessary. After that the chunkText is added to the value of the phraseId.


I hope you enjoyed reading about the TranslatedText component, and I look forward to any comments in the section below.


Happy hacking 🙂


To conclude


Attila has placed the component in the directory: https://github.com/am-houtkooper/translated-text-design-studio-component

We have not tried to place it in the SDK Development Community yet, maybe later.

To report this post you need to login first.

9 Comments

You must be Logged on to comment or reply to a post.

  1. Nikunj Goel

    Hello Arwold Koelewijn,

    We have similar requirement in our project and we have divided data more than 60 characters into chunks of 60 characters.

    Next to it we had developed a SDK component according to code scpirt mentioned above and then add it into Design Studio but we are trying to display data nothing is displayed.

    For Design Studion, version is 1.5 that we are using.

    Could you please help us in this topic.

    Waiting for reply,

    Many thanks in advance,

    Nikunj

    (0) 
    1. Arwold Koelewijn Post author

      Hello Nikunj,

      We are on DS 1.5 as well, and it is still working. Does your query have 4 columns?

      1: language (i.e. EN)

      2: identifier (i.e. T142)

      3: number (1….)

      4: chunks of 60 char text

      Best regards,

      Arwold

      (0) 
      1. Nikunj Goel

        Hello Arwold,

        yes we are having same fields in our data source but now it is just showing ‘Translated text will be displayed here.’ as always so could you please suggest the possible error

        In java Script we have written:

        sap.designstudio.sdk.Component.subclass(“com.sap.sample.coloredbox.ColoredBox”, function() {

          var that = this;

          var customComponent_language = “EN”;

          var customComponent_phrase = “PHRASE_EXAMPLE”;

          var customComponent_data;

          var customComponent_translations = {

          “EN”: {

          “PHRASE_EXAMPLE”: “Translated text will be displayed here.”

          },

          “NL”: {

          “PHRASE_EXAMPLE”: “Vertaalde tekst wordt hier getoond.”

          }

          };

          var customComponent_metadata = undefined;

          function initDevContainer() {

          dev_container = document.body.appendChild(document.createElement(“div”));

          var height = 100;

          var width = 600;

          dev_container.style.height = height;

          dev_container.style.width = width;

          }

          function getContainerElement() {

          // INDEPENDENTDEVELOPMENT:

          return that.$()[0];

        // return dev_container;

          }

          this.init = function () {

          // INDEPENDENTDEVELOPMENT:

        // initDevContainer();

          var containerElement = getContainerElement();

          containerElement.className += ” ColoredBox_container”;

          containerElement.appendChild(document.createTextNode(“…”));

          // Render the default values.

          drawCustomComponent();

          };

          this.afterUpdate = function () {

          drawCustomComponent();

          };

          function drawCustomComponent() {

          if (customComponent_translations) {

          var containerElement = getContainerElement();

          var phraseElement;

          if (customComponent_translations[customComponent_language] === undefined || customComponent_translations[customComponent_language][customComponent_phrase] === undefined) {

          var errorMessage;

          if (customComponent_translations[customComponent_language] === undefined) {

          errorMessage = “No data for language ‘” + customComponent_language + “‘”;

          }

          else {

          errorMessage = “No data for phrase ‘” + customComponent_phrase + “‘ in language ‘” + customComponent_language + “‘”;

          }

          phraseElement = document.createElement(“span”);

          phraseElement.setAttribute(“class”, “error”);

          phraseElement.appendChild(document.createTextNode(errorMessage));

          } else {

          phraseValue = customComponent_translations[customComponent_language][customComponent_phrase];

          phraseElement = document.createTextNode(phraseValue);

          }

          containerElement.replaceChild(phraseElement, containerElement.firstChild);

          }

          };

          this.language = function (value) {

          if (value === undefined) {

          return customComponent_language;

          }

          else {

          customComponent_language = value;

          }

          };

          this.phrase = function (value) {

          if (value === undefined) {

          return customComponent_phrase;

          }

          else {

          customComponent_phrase = value;

          }

          };

          this.data = function (resultSet) {

          if (resultSet === undefined) {

          return customComponent_data;

          }

          else {

          customComponent_data = resultSet;

          // It is assumed to be chopped up into chunks of max 60 characters each.

          // These chunks are concatenated and stored for drawing.

          customComponent_translations = [];

          var previousDataKey = undefined;

          // Keeps all items in chunked form.

          var chunkedItems = [[]];

          for (var i = 0; i < resultSet[“axis_rows”].length; i++) {

          var valueIndexRow = resultSet[“axis_rows”][i];

          var languageIndex = valueIndexRow[0];

          var languageValue = resultSet[“dimensions”][0][“members”][languageIndex][“text”];

          var phraseIndex = valueIndexRow[1];

          var phraseValue = resultSet[“dimensions”][1][“members”][phraseIndex][“text”];

          var chunkTextIndex = valueIndexRow[3];

          var chunkTextValue = resultSet[“dimensions”][3][“members”][chunkTextIndex][“text”];

          // Add the chunk to the chunked items.

          if (customComponent_translations[languageValue] === undefined) {

          customComponent_translations[languageValue] = {};

          }

          if (customComponent_translations[languageValue][phraseValue] === undefined) {

          customComponent_translations[languageValue][phraseValue] = “”;

          }

          customComponent_translations[languageValue][phraseValue] += chunkTextValue;

          }

          return this;

          }

          };

          this.metadata = function (value) {

          if (value === undefined) {

          return customComponent_metadata;

          }

          else {

          customComponent_metadata = value;

          return this;

          }

          };

        // INDEPENDENTDEVELOPMENT:

        //}

        });

        Regards,

        Nikunj

        (0) 
        1. Attila Houtkooper

          Hi Nikunj,

          I wrote the TranslatedText code you’re trying to run. I think I may be able to help you out.

          You’re well on your way; I tested the code you are using in INDEPENDENTDEVELOPMENT mode, and it is works fine. Notice that by default the language is EN and the default phrase is “Translated text will be displayed here.”. To translate this phrase you can change the language by using setLanguage(“NL”). This translates the default translate given in the component.

          A thing that I notice is that the module name is still ColoredBox, you might want to change that eventually.

          When your data source is configured correctly you will be using your own phrases and any number of languages.

          Let me know if there are any issues with this.

          Kind regards,

          Attila

          (0) 
          1. Nikunj Goel

            Hello Attila,

            Many Thanks for your remarks.

            Actually I just want to concatenate the strings into 1 and when I configure my data source, it does not take Phrase and language from data source(Bex Query) and return me the text “Translated text will be displayed here” instead of concatenated string.

            and I also I created component with name “ColoredBox”, do I need to change this somewhere?

            Regards,

            Nikunj

            (0) 
            1. Attila Houtkooper

              Hi Nikunj,

              It’s hard for me to exactly pinpoint your problem. In general there are two things that are worth checking out:

              1. Did you set all parameters? So both the language and the phraseId
              2. What is the shape of your input data? If you have all phrases consisting of exactly one row, which is pretty ideal, then still the component should work.

              The name is not interesting, you can change it in the JavaScript code, but you’ll also have to change it in the rest of the component configuration, so for now I would leave it.

              You can use debugging to see what kind of data the component receives by adding the following line in the data(resultSet) function:

              console.log(“resultSet”, resultSet);

              When you run the component inside of an application in a browser, the web developer console (F12 in Firefox) will output that information on startup. That should give you insight into what is given.

              Kind regards,

              Attila

              (0) 
    1. Attila Houtkooper

      Hi Karol,

      I wrote the component we described in this article. I conferred with Arwold, I will be facilitating getting the code applied for the community package. Can you indicate how to do this?

      Kind regards,

      Attila

      (0) 

Leave a Reply