How can I create a multi-lanaguage IdP Selection Page?

Problem

We are using the SAML App for our Atlassian platform. Would it be possible to have a multi-language version of the 'IdP Selection Page Template' (SAML Single Sign On Plugin Configuration → Page Templates)?

Solution

We currently don't have an option in the UI to support multi-language templates. However, you can use JavaScript inside the IdP Selection templates to achieve your goal.

SAML_Page_Template

Below you will find an example based on our default page template. Our example contains the languages German (de) and French (fr), however you can change or add the languages to your needs.

Multilanguage Template

<html>
 <head>
  <title>Select Identity Provider</title>
  $webResourceManager.requireResource("com.atlassian.auiplugin:aui-spinner")
  $webResourceManager.requireResource("$pluginproperties.pluginkey:resources")
  <meta name="decorator" content="atl.general">
 </head>
 <body class="aui-layout aui-theme-default page-type-message" >
  <section id="content" role="main">
   <div class="aui-page-panel"><div class="aui-page-panel-inner">
    <section class="aui-page-panel-content">
     <div style="display: flex; justify-content: center;" id="initial-load-spinner">
        <aui-spinner size="large"></aui-spinner>
     </div>
     <div class="form-body" style="display: none;" id="form-body">
     <h1 data-translate="title">Select your Identity Provider</h1>
     #if($idpSelected)
      <p><span data-translate="wait">Select or wait 3 seconds to use $selectedName</span> <span class="aui-icon aui-icon-wait"></span></p>
        <script>
          var timeout = setTimeout("location.href = '$selectedUrl';", 3000);
          window.onclick= function () { clearTimeout(timeout); } 
        </script>
      #end
      #foreach($idp in $idps)
      <p>
        <a href="$idp.ssoUrl" data-translate="$idp.name">$idp.name</a> $idp.description
      </p>
      #end
      #if($loginurl)      <p>
       <a href="$loginurl" data-translate="nosso">Login with username and password</a>
      </p>
      #end
     </div>
   </section>
  </div>
 </div>
</section>
<script>
    AJS.toInit(function() {
        //                   TRANSLATION SETTINGS
        // The key to this template is that elements above whose text is supposed to be translated all have this data-translate attribute, where they get a translation key
        // The following map contains mappings from languages to translation keys to the actual translations.
        
        // Add your translations here. The keys ('de', 'fr') can be either locales ('de-DE', 'de-AT') or languages ('de', 'fr'). When in doubt, just use the language it's going to be used as a fallback
        var translations = {
            de: {
                title: "Wählen Sie Ihren Identity Provider",
                wait: "Wählen Sie einen Identity Provider aus oder warten Sie 3 Sekunden um $selectedName zu benutzen",
                nosso: "Login mit Benutzername und Passwort",
                // IdP names are allowed as well
                // If they contain non-alphanumeric characters, you'll have to write the name in "quotes"
                // the keys are case-sensitive!!!
                "Your IdP name": "Ihr IdP-Name"
            },
            fr: { // This translation has been generously provided by Google Translate, I would not trust it, but it's here as an example.
                title: "Choisissez votre fournisseur d'identité",
                wait: "Sélectionnez un fournisseur d'identité ou attendez 3 secondes pour utiliser $selectedName",
                nosso: "Connectez-vous avec nom d'utilisateur et mot de passe",
                "Your IdP name": "Votre nom IdP"
            }
        };
        // This is supposed to be the language present in the fields that are given above
        var defaultLanguage = "en";

        //                   TRANSLATION CODE
        // This is where the magic happens. Ideally, you shouldn't need to touch the code after this.
        
        // Detect user language with fallbacks for different levels of old browsers
        // This will be ordered from most preferred to least preferred
        var userLanguages = navigator.languages || [navigator.language] || [navigator.userLanguage] || [];
        
        // ensure default language can also match.
        translations[defaultLanguage] = {};
        
        var matchedLanguage = null;

        // Check if one the user's preferred languages is available
        userLanguages.forEach(function (languageString) {
            if (matchedLanguage === null && languageString in translations) {
                matchedLanguage = languageString
            }
        });
        
        // if no match was found, try converting the locale strings into language strings and see if that helps
        if (matchedLanguage === null) {
            userLanguages.forEach(function (languageString) {
                try {
                    var splitLanguageString = languageString.split('-')[0];
                    if (matchedLanguage === null && typeof splitLanguageString === 'string' && splitLanguageString in translations) {
                        matchedLanguage = splitLanguageString;
                    }
                } catch (e) {
                    console.warn("Error preparing user language");
                }
            });
        }
        
        // if any (non-default language) match was found, we set the translated strings in the UI
        if (matchedLanguage !== null && matchedLanguage !== defaultLanguage) {
            var translationsToUse = translations[matchedLanguage];
            AJS.$('[data-translate]').each(function() {
                var elem = AJS.$(this);
                var translationToUse = translationsToUse[elem.attr('data-translate')];
                if (!translationToUse) {
                    console.warn("Did not find a", matchedLanguage, "translation for", elem.attr('data-translate'));
                    return;
                }
                elem.text(translationToUse);
            })
        }
        // and now display the UI
        AJS.$("#initial-load-spinner").hide();
        AJS.$("#form-body").show();
    })
</script>
</body>
</html>
JS

A little more background / explanation

The script basically extends every element with text in it to have an attribute (data-translate) that contains a “translation key”, while the text is kept as the default language. After loading the page, it executes a JavaScript block. This contains the translations you want to have for each language and translation key (you can modify this according to the example and your need). Afterwards, there’s code that detects the user’s preferred language and does the substitution if necessary.

We have included extensive comments in the code. However, if you are not sure or need help please contact our Support or book a free screen share session.