Multilingual portals

1. General

The two-character language code according to ISO 3166-1 is the basis for controlling the language in portals. Multilingual does not just mean presenting texts in a language-dependent manner, but also presenting number and date values in the respective countr. Even colors can be interpreted differently depending on the culture. Graphics with lettering also get a language-dependent aspect. Even the question whether the user reads from left to right or vice versa can play a role. There are therefore various details that need to be considered and implemented when creating a multilingual portal - regardless of which technology is used. You'll find what you need to know for this here.

2. Portal properties - Regional settings




In the portal properties, accessible via the Portal menu / Portal properties, you will find the Regional settings. The portal can be configured for international use here. Information about each of the options can be found here:

3. Language switch in the browser




The additional control Language switch from the Design module is used for switching the language in the browser. The languages, which should be selectable here, must be activated in the Regional settings.



To switch to another format, used to format, for example, the date according to the region, the additional control Locale switch from the Design module is used. The formats, which should be selectable here, must be activated in the Regional settings.

4. Find and translate element titles

In the modules Applications, Design and Processes you can search for elements that are missing titles in one of the portal languages. The full list of element titles from applications, processes and relationship diagrams can be exported as an XML file, translated and then reimported via the respective main menu.

5. Language switch in modules

You can switch the language that the element titles in each of the modules Portal Manager are displayed in, with the globe symbol in the symbol bar.

6. Portal menu




For applications, which are integrated into the menu structure, you can define that the application title should be used as the menu item. If the title is already defined in multiple languages, the values for each of the portal languages will be used. If the Use title of application setting is disabled, you need to define the title for the menu item in each of the portal languages. You can also define multilingual titles for all other objects such as menu folders, links or separators, these will then be displayed in the language selected by the user.

7. Layout




Because each portal layout is an assembly of various containers that, especially for barrier-free concepts, should be understood by screenreaders, the containers also need to be provided with multilingual titles. Custom language constants cannot be used in the layout. Each of the elements in the layout uses correspondingly defined portal constants.

8. User settings

In the Users module, regional-specific settings can be defined in the properties of users.



The settings defined here for time zone, language, format and layout overrule the settings made in the Regional settings of the portal.

9. Applications


9.1 Language constants in applications

In the Applications module, using language constants is not only a good idea for multilingual applications but all applications can benefit from the use of the constants. Because the same text appears in multiple locations, these can be modified much more easily via a central constant. Therefore, United Planet recommends developing applications from the very beginning with application constants. Application constants can be added, edited and deleted via the Application menu / Manage language constants.

9.2. Multilingual element titles

You can find out how to provide elements with multilingual titles here.

9.3. Multilingual list entries

For the elements you can select User-defined values as the source for the list in the element.



Constants can also be used here.



The displayed value from the constant and the saved value must be identical in this case.

9.4. Multilingual data records

You can discover how to enter data records in different languages and then display them correctly based on the respective portal language setting in the Advanced Techniques entry Multilingual data records.

9.5 Multilingual data with doubled primary key

In order to make data available in multiple languages, these can be saved in a data group using the principle of a doubled primary key. This is made up of a key and an ISO language code. For control tables, e.g. for statuses, the key should be a descriptive string. For all other data, it should be a GUID.



Before saving the application, you can changed the data type of the existing primary key to string on the Expert tab, meaning it will be saved as a GUID later. For the second key, a new data field called "Language" is created, again before the application is published.



On the Expert tab, the expert attribute "primarykey" is set to "yes".



When data records are entered, both primary keys need to be populated with values so that errors do not occur when the data is processed. To do that, the language data field is connected to a edit field in the Hidden area. The field is provided with a "customdefault" expert attribute with the value "$lang" and is preallocated with the current portal language .



If the primary key should also be generated automatically using a GUID, the value "$Unique.newGuid()" can be entered for the "customdefault"; this field can also be moved to the Hidden area.

A common scenario for this is using multilingual master data, such as statuses, categories, in moving data records via a reference. If a reference to a data group with two primary keys is created, the session variable "Language" must be assigned for the primary key. The other primary key will be left with the setting "Will be created automatically".



Using the reference, Intrexx will now always provide the corresponding language text in the browser.

8.6 Multilingual data with a core data record as a reference

This option makes sense, if a large amount of data, which is language-independent, is defined alongside multilingual texts. This means that the language-independent data is recorded in the core data record and the text information is stored in the data record.

9.7. Multilingual tree controls

The texts for labelling a multilingal tree element are managed in their own data group with doubled primary key.



The text data group is referenced in the Tree data group and the language is set to Current portal language. Here, it is important that the entries saved in the labelling table receive the ID of the tree entry as the primary key in order to create a synchronicity between both of the data groups and the data records.



In the Tree control's configuration, the label text will be assigned as the title using the reference. This will be identified and then displayed, based on the defined portal language.



When creating a new tree entry, a special procedure must be implemented in this constellation because a label is not saved in the tree's data group. For this reason, an edit page with a text edit field without data link is created.



When Save is clicked, a JavaScript with the entered text will be transferred to the process as a request parameter.
function newTreeEntry(oAction)
{
  var oDescription = getElement("GUID_EDITFIELD");
  oAction.oUp.oTarget.addParam = Helper.setQsValueByParam(
  "rq_customDescription", oDescription.value, oAction.oUp.oTarget.addParam);
  return true;
}
Using a Data group event handler, which monitors whether a new tree entry has been made, the request value is read in the executed Groovy script, the corresponding entry is added to the labelling table and at the same time, the data records for the language variables are created. Furthermore, the reference value is written to the data record in the labelling table in the tree's data group.
def conn = g_dbConnections.systemConnection

def l_strActualLang = g_language
def l_strDescription = g_request.get("rq_customDescription")
def l_intTreeId = g_record["GUID_ID_TREEDATASET"].value

def l_aLanguages = g_portal.defaultLocale.languages

// Generate data records
l_aLanguages.each { language ->
  def l_strIsoLang = language
  g_dbQuery.executeUpdate(conn, "INSERT INTO
  DATAGROUP('GUID_DATAGROUP_LABELLINGTABLE') 
  (TREEID, LANG, STRNAME) VALUES (?,?,?)") {
    setInt(1, l_intTreeId)
    setString(2, l_strIsoLang)
    if(l_strIsoLang == l_strActualLang)
    {
      setString(3, l_strDescription)
    }
    else
    {
      setString(3, "Translate: " + l_strDescription)
    }
  }
}

g_dbQuery.executeUpdate(conn, "UPDATE
DATAGROUP('GUID_DATAGROUP_TREE') SET LID = ?, 
REF_NAME = ? WHERE LID = ?") {
  setInt(1, l_intTreeId)
  setInt(2, l_intTreeId)
  setInt(3, l_intTreeId)
}

10. Create language variables via a process

Usually, a multilingual data record is always created based on one language. If the creator has set their portal to English, the data record is created in English. Additional language variables must then be generated. This can be done manually but represents a lot of work for multilingual portals. A process can take over the preliminary work and automatically generate the data records for the respective language variables for each language in the portal. This makes it easier to add additional portal languages at a later point in time. Even in this case, a process can create the data records for the new language variables retroactively. This is especially important for master data, such as status definitions, to ensure that the application is fully operational in the new language.



The following Groovy script demonstrates, by way of example, how to create data records for missing language variables in a data group when a new portal language is added.
def conn = g_dbConnections.systemConnection

// Identifies the currently available portal languages
def l_aLanguages = g_portal.defaultLocale.languages

// For each portal language
l_aLanguages.each
{
  // Check whether at least one entry for this language is found in 
  // an application table
  def l_strIsoLanguage = it
  def l_intLanguageDetect = g_dbQuery.executeAndGetScalarIntValue(conn, 
  "SELECT COUNT(*) FROM DATAGROUP('GUID_DATAGROUP') WHERE LANG = ?", 0) {
    setString(1, l_strIsoLanguage)
  }

  if(l_intLanguageDetect == 0)
  {
    // Language not found in data, add language Variants
    def stmtData = g_dbQuery.prepare(conn, 
    "SELECT STRID FROM 
    DATAGROUP('GUID_DATAGROUP')")
    def rsData = stmtData.executeQuery()

    while (rsData.next())
    {
      def l_strTypeId = rsData.getStringValue(1)
      g_dbQuery.executeUpdate(conn, "INSERT INTO
      DATAGROUP('GUID_DATAGROUP') 
      (TREEID, LANG, STRNAME) VALUES (?,?,?)") 
      {
        setString(1, l_strTypeId)
        setString(2, l_strIsoLanguage)
        setString(3, "Translate")
      }
    }
    rsData.close()
    stmtData.close()
  }
}
The language-isolated-search takes the current portal language and only returns content that corresponds to this language in the search results. To do that, the portal language needs to be filtered in the search configuration. The "Language" data field is compared to the system value "Language". The respective search results will only be shown if these match.

11.2 Search with user-selected language

You can enable the user to select the language by using facets. To begin with, the facet "Language" should be defined.



In the search configuration, the facet is connected to the "Language" data field.



The configuration now provides every language, which was found during the search, as an option for filtering the search results. Because the search engine cannot interpret the facets, the ISO codes are shown as the options.

12. Multilingual emails

12.1 Email to one recipient

If a user sends an email, their language setting can be identified in a process. The process can use the identified language in an email sent with an Email action.



The subject can be defined by translating the language-dependent subject. To make the language control more individual, the language can be identified and then written to the processing context with a Groovy script before the Email action is performed.
// Language of current user
def l_strLanguage = Locale.forLanguageTag(g_language).getDisplayLanguage(new Locale(g_language))

// Language of any user 
// (via their GUID and user object)
def l_objUser = g_om.getUser(l_strUserGuid)
def l_strLanguage = objUser.getDefaultLanguage()

// Write language to processing context
g_sharedState.maillanguage = strLanguage
In the properties dialog of the email action, the parameter "language" can be added on the Expert tab. The identified language can be transferred as the value with "urn.sharedState.maillanguage".



The subject can also be defined using a language constant. With the corresponding Groovy method, the language constant's content is read based on the language identified in the previous step.

// Identify subject from language constant and write 
// to processing context
g_sharedState.subject = g_i18n.application("APP_GUID").language(l_strLanguage)["MYMAILSUBJECT"]
The value from the processing context is then entered in the static, language-independent subject of the Email action with "urn.sharedState.subject".



If the email is sent via CC and BCC to one recipient with different languages in each case, this method cannot be used. This would require separate handling with an individual email action in each case in the process chain. The message text could also be identified in a preceding Groovy action and then written to the processing context. The processing context can then be read in Velocity and from that, the email body can be generated.

12.2 Email to distribution list

There are two approaches to sending emails to a distribution list if these should be sent multilingually: A possible construction is shown below. In this case, however, only user objects can be recipients via the distribution list.
import de.uplanet.lucy.util.TextUtil
import de.uplanet.lucy.server.mail.GroovyMailBuilder
import de.uplanet.lucy.server.mail.MailUtil
import de.uplanet.lucy.server.portalserver.PortalServerPath
import de.uplanet.lucy.server.composer.UrlBuilder

// Content of a distribution control (Recipient GUIDs)
def l_strRecipients = g_record["640...DAC"].value
def l_aReceipients = TextUtil.stringToList(strRecipients)

aReceipients.each {
  def strEmail = g_dbQuery.executeAndGetScalarStringValue(conn, "SELECT
  STRMAILBIZ FROM VBLUSER WHERE STRGUID = ?", null) {
    setString(1, it)
  }
  if(strEmail != null && strEmail != "")
  {	
    def mail = new GroovyMailBuilder().composeMail {
	  headers = [
		"X-IX-Share": "intrexx-share-notification",
	]

	from        = MailUtil.getDefaultSenderAddress()
	to          = strEmail
	subject     = l_mailHelper.getMailTitle()
	contentType = "text/html; charset=UTF-8"

	body << """<html>  … </html>"""
    }
    mail.drop()
  }
}

13. Language switches

To a large extent, you can avoid using language switches in programming codes with language constants. However, the following constructs could be useful in special cases. To create a language switch in JavaScript, the selected portal language needs to be identified with the "oHtmlRoot" object.
var l_lang = oHtmlRoot.oUp.oFormatInfo.lang;

switch(l_lang)
{
  case "de":
       // Code for German
       break;
  case "en":
       // Code for English
       break;
  :

  default:
       // Fallback, if the language is missing (default portal language)
}

if(l_lang == "de")
{
  // Code for German
}
else if(l_lang == "en")
{
  // Code for English
}

:

else
{
  // Fallback, if the language is missing (default portal language)
}
The "oHtmlRoot" object can also be used to identify the defined default language.
var l_defaultLang = oHtmlRoot.oUp.oFormatInfo.defaultLang;

To construct language-dependent switches in the Velocity context,
the Intrexx system variable $lang can be used that contains the current
portal language.

#if($lang == "de")
  // Language-dependent code
#elseif($lang == "en")
  // Language-dependent code
#else
  // Fallback for missing language
#end
Different options are available for generating multilingual data in a Groovy script or to control certain actions based on the language.
// Current portal language
g_language
// Default portal language
g_defaultLanguage
// Current portal language via the request parameter rq_Lang 
g_request.get("rq_Lang")
// User's default language from the current session
g_session.user.getDefaultLanguage()
A language switch can be created for various purposes using a switch case construction.
switch(g_language)
{
  case "de":
    return german
    break
  case "en":
    return english
    break
  default:
    return english
}

14. Multilingual graphics

If graphics are managed via data groups, these can be defined for each language-dependent data record. It's much more difficult to display language-dependent graphics in the layout, as this is not provided for technically. One option for this is to provide the graphic with the ISO code in the filename and then to construct the URL with Velocity using the current portal language. The following ActionControl generated a DIV container with a link to the portal homepage as well as the language-dependent embedding of a graphic. It's important to name the graphic with "logo_<iso-languagecode>.png". The control assembles the filename dynamically by using the current portal language "$lang".
<div id="Container_Logo">
  <a title="Home" href="/$Portal.getPortalName()/default.asp">
    <img width="199" height="49" src="images/${layout}/logo_${lang}.png" alt="Home">
  </a>
</div>

15. Velocity-variables in language constants

In every language constant with the type Constant is evaluated as a Velocity expression, Velocity variables can be embedded when these are used on application pages. For this to work correctly, the variable needs to be available in the page context. In the following example, the variable "$Rooms" is written into the text to add a dynamic statement - the number of rooms - to the static text.

Constant text:
There are $Rooms room(s) for this building!
Output: There are 10 room(s) for this building!

16. Language switch with flags

A language switch with flags only makes sense for a few portal languages, and only in combination with the language name. For languages such as English, that are represented by multiple countries and therefore flags, a language switch with flags is not always ideal. Intrexx already delivers a set of graphics with flags that possess the ISO code as their name. To begin with, add a new file called "languageswitch_flag.vm" to the portal directory \internal\system\vm\html\actioncontrol and copy the Velocity code below into this file.
#set($l_aAllLang = $WebMenu.getMenuLanguages())
<span id="ID_Languageswitch">
#foreach($l_strLang in $l_aAllLang)
 #set($l_strImagePath = "images/assets/flags_of_the_world/16x16/plain/${l_strLang}.png")
 #set($l_strDisplayLang = $TextUtil.getDisplayLanguage($l_strLang, $l_strLang))
 <a style="width: 16px; height: 16px; display: inline-block; background-repeat: no-repeat; background-position: center; background-image: url($l_strImagePath); cursor: pointer;"
 onclick="var oLangAction=setLangAction('$velocityCount', '$!l_strLang');oLangAction.changeLang();return false;" lang="$!l_strLang" target="_top" href="$Request.get('DEFAULT_URL')?rq_Lang=$!l_strLang" id="ID_actionLangSwitch${velocityCount}" title="$I18N.get('LANGUAGESWITCH_DESCRIPTION')"></a>
#end
</span>
<script type="text/javascript">function setLangAction(count, lang){oLangAction = new upTextActionControl();oLangAction = oLangAction.biDirectUpHtml(oLangAction, 'ID_actionLangSwitch' + count);oLangAction.linkType = '4';oLangAction.oTarget = new upTarget();oLangAction.oTarget.rq_Lang = lang;return oLangAction;}
</script>
In the file "controls.xml", add the following block to the XML structure:
<control id="Action_LanguageSwitchFlag" mobile="false" multipleUse="false"
  vm="languageswitch_flag.vm">
  <title lang="en" value="Language switch with flags"/>
  <title lang="de" value="Sprachumschalter mit Flaggen"/>
  <description lang="en" value="Language switch"/>
  <description lang="de" value="Sprachumschalter"/>
</control>
After restarting the Portal Manager, the additional control can be selected accordingly and added to the layout.

17. XML export format for language constants

The XML format for importing and exporting language constants collects every text constant together in the block <texts>. Every constant is defined via an <element>. The constant name is stated as the key, whereas the value of the "type" always needs to be "constant". Every constant is defined via an <element>. Each language variable is written as a <text> element under the <element>. As well as the ISO code, the availability ("ALL", "VELOCITY", "JAVASCRIPT") of the constant is also defined in the "language" parameter. Language texts defined directly in the controls, are identified in the <element> tag with the parameters "type" (object type) and "key" (control GUID). In the <text> tag, further information about the export is stated in the paremeters "timestamp" (export time stamp), (application or portal), "guid" (application GUID) and "refLanguage" (reference language).

Parameter Value Description
timestamp YYYYMMTTHHMMSS Export time stamp
type application portal States whether the export comes from an application or a portal
guid GUID Application GUID
refLanguage ISO language code Reference language
version Integer value Export version
<?xml version="1.0" encoding="UTF-8"?>
<texts timestamp="20160212155654" guid="798…1F1"
 type="application" version="1" refLanguage="de">
    <element key="ADDITIONAL_DISTRIBUTION_LIST" type="constant">
        <text language="de" type="VELOCITY">Zusätzlicher Verteiler</text>
        <text language="en" type="VELOCITY">Additional Distribution List</text>
    </element>
    <element type="dropdowncontrol" key="C22…D65">
        <text type="title" language="de">Auswahlliste</text>
        <text type="title" language="en">Drop-down list</text>
    </element>
<texts>