diff --git a/lib/SimpleSAML/IdP.php b/lib/SimpleSAML/IdP.php
index 7b9ea5be373bd2911e902960adf65ffd706e8fb7..9fce99741424bb5778db0e47c5494416043626b4 100644
--- a/lib/SimpleSAML/IdP.php
+++ b/lib/SimpleSAML/IdP.php
@@ -169,6 +169,8 @@ class IdP
 
     /**
      * Get SP name.
+     * Only usefd in IFrameLogout it seems.
+     * TODO: probably replace with template Template::getEntityDisplayName()
      *
      * @param string $assocId The association identifier.
      *
diff --git a/lib/SimpleSAML/Locale/Language.php b/lib/SimpleSAML/Locale/Language.php
index 6d3cd9d5dcbe21be671266cb58fc02b9fbb186c0..3ae24285f5ec4c47b0ac774a71a4a3fe07cb5eab 100644
--- a/lib/SimpleSAML/Locale/Language.php
+++ b/lib/SimpleSAML/Locale/Language.php
@@ -51,6 +51,13 @@ class Language
      */
     private string $defaultLanguage;
 
+    /**
+     * The final fallback language to use when no current or default available
+     *
+     * @var string
+     */
+    public const FALLBACKLANGUAGE = 'en';
+
     /**
      * An array holding a list of languages that are written from right to left.
      *
@@ -146,7 +153,7 @@ class Language
     {
         $this->configuration = $configuration;
         $this->availableLanguages = $this->getInstalledLanguages();
-        $this->defaultLanguage = $this->configuration->getString('language.default', 'en');
+        $this->defaultLanguage = $this->configuration->getString('language.default', self::FALLBACKLANGUAGE);
         $this->languageParameterName = $this->configuration->getString('language.parameter.name', 'language');
         $this->customFunction = $this->configuration->getArray('language.get_language_function', null);
         $this->rtlLanguages = $this->configuration->getArray('language.rtl', []);
@@ -166,7 +173,7 @@ class Language
      */
     private function getInstalledLanguages(): array
     {
-        $configuredAvailableLanguages = $this->configuration->getArray('language.available', ['en']);
+        $configuredAvailableLanguages = $this->configuration->getArray('language.available', [self::FALLBACKLANGUAGE]);
         $availableLanguages = [];
         foreach ($configuredAvailableLanguages as $code) {
             if (array_key_exists($code, self::$language_names) && isset(self::$language_names[$code])) {
@@ -377,6 +384,16 @@ class Language
         return in_array($this->getLanguage(), $this->rtlLanguages, true);
     }
 
+    /**
+     * Returns the list of languages in order of preference. This is useful
+     * to search e.g. an array of entity names for first the current language,
+     * if not present the default language, if not present the fallback language.
+     */
+    public function getPreferredLanguages(): array
+    {
+        $curLanguage = $this->getLanguage();
+        return array_unique([0 => $curLanguage, 1 => $this->defaultLanguage, 2 => self::FALLBACKLANGUAGE]);
+    }
 
     /**
      * Retrieve the user-selected language from a cookie.
@@ -386,7 +403,7 @@ class Language
     public static function getLanguageCookie(): ?string
     {
         $config = Configuration::getInstance();
-        $availableLanguages = $config->getArray('language.available', ['en']);
+        $availableLanguages = $config->getArray('language.available', [self::FALLBACKLANGUAGE]);
         $name = $config->getString('language.cookie.name', 'language');
 
         if (isset($_COOKIE[$name])) {
@@ -399,7 +416,6 @@ class Language
         return null;
     }
 
-
     /**
      * This method will attempt to set the user-selected language in a cookie. It will do nothing if the language
      * specified is not in the list of available languages, or the headers have already been sent to the browser.
@@ -410,7 +426,7 @@ class Language
     {
         $language = strtolower($language);
         $config = Configuration::getInstance();
-        $availableLanguages = $config->getArray('language.available', ['en']);
+        $availableLanguages = $config->getArray('language.available', [self::FALLBACKLANGUAGE]);
 
         if (!in_array($language, $availableLanguages, true) || headers_sent()) {
             return;
diff --git a/lib/SimpleSAML/XHTML/IdPDisco.php b/lib/SimpleSAML/XHTML/IdPDisco.php
index e3c0517ad974fa85aa73b66f734aa0f2493b783f..39d475e8750ad9e25ca7f212a99be92bf965a4f6 100644
--- a/lib/SimpleSAML/XHTML/IdPDisco.php
+++ b/lib/SimpleSAML/XHTML/IdPDisco.php
@@ -589,30 +589,12 @@ class IdPDisco
 
         $t = new Template($this->config, $templateFile, 'disco');
 
-        $fallbackLanguage = 'en';
-        $defaultLanguage = $this->config->getString('language.default', $fallbackLanguage);
-        $translator = $t->getTranslator();
-        $language = $translator->getLanguage()->getLanguage();
-        $tryLanguages = [0 => $language, 1 => $defaultLanguage, 2 => $fallbackLanguage];
-
         $newlist = [];
         foreach ($idpList as $entityid => $data) {
             $newlist[$entityid]['entityid'] = $entityid;
-            foreach ($tryLanguages as $lang) {
-                if ($name = $this->getEntityDisplayName($data, $lang)) {
-                    $newlist[$entityid]['name'] = $name;
-                    continue;
-                }
-            }
-            if (empty($newlist[$entityid]['name'])) {
-                $newlist[$entityid]['name'] = $entityid;
-            }
-            foreach ($tryLanguages as $lang) {
-                if (!empty($data['description'][$lang])) {
-                    $newlist[$entityid]['description'] = $data['description'][$lang];
-                    continue;
-                }
-            }
+            $newlist[$entityid]['name'] = $t->getEntityDisplayName($data);
+
+            $newlist[$entityid]['description'] = $t->getEntityPropertyTranslation('description', $data);
             if (!empty($data['icon'])) {
                 $newlist[$entityid]['icon'] = $data['icon'];
                 $newlist[$entityid]['iconurl'] = $httpUtils->resolveURL($data['icon']);
@@ -640,22 +622,4 @@ class IdPDisco
         $t->data['rememberchecked'] = $this->config->getBoolean('idpdisco.rememberchecked', false);
         $t->send();
     }
-
-
-    /**
-     * @param array $idpData
-     * @param string $language
-     * @return string|null
-     */
-    private function getEntityDisplayName(array $idpData, string $language): ?string
-    {
-        if (isset($idpData['UIInfo']['DisplayName'][$language])) {
-            return $idpData['UIInfo']['DisplayName'][$language];
-        } elseif (isset($idpData['name'][$language])) {
-            return $idpData['name'][$language];
-        } elseif (isset($idpData['OrganizationDisplayName'][$language])) {
-            return $idpData['OrganizationDisplayName'][$language];
-        }
-        return null;
-    }
 }
diff --git a/lib/SimpleSAML/XHTML/Template.php b/lib/SimpleSAML/XHTML/Template.php
index f9df3c72cda1e154ec698ec6afb8b96507b8fbcf..5921db0597d4e66d367e0fee3d22aae5ffcd48d9 100644
--- a/lib/SimpleSAML/XHTML/Template.php
+++ b/lib/SimpleSAML/XHTML/Template.php
@@ -583,4 +583,47 @@ class Template extends Response
     {
         return $this->translator->getLanguage()->isLanguageRTL();
     }
+
+    /**
+     * Search through entity metadata to find the best display name for this
+     * entity. It will search in order for the current language, default
+     * language and fallback language for the DisplayName, name, OrganizationDisplayName
+     * and OrganizationName; the first one found is considered the best match.
+     * If nothing found, will return the entityId.
+     */
+    public function getEntityDisplayName(array $data): string
+    {
+        $tryLanguages = $this->translator->getLanguage()->getPreferredLanguages();
+
+        foreach($tryLanguages as $language) {
+            if (isset($data['UIInfo']['DisplayName'][$language])) {
+                return $data['UIInfo']['DisplayName'][$language];
+            } elseif (isset($data['name'][$language])) {
+                return $data['name'][$language];
+            } elseif (isset($data['OrganizationDisplayName'][$language])) {
+                return $data['OrganizationDisplayName'][$language];
+            } elseif (isset($data['OrganizationName'][$language])) {
+                return $data['OrganizationName'][$language];
+            }
+        }
+        return $data['entityid'];
+    }
+
+    /**
+     * Search through entity metadata to find the best value for a
+     * specific property. It will search in order for the current language, default
+     * language and fallback language; if not found it returns null.
+     */
+    public function getEntityPropertyTranslation(string $property, array $data): ?string
+    {
+        $tryLanguages = $this->translator->getLanguage()->getPreferredLanguages();
+
+        foreach($tryLanguages as $language) {
+            if (isset($data[$property][$language])) {
+                return $data[$property][$language];
+            }
+        }
+
+        return null;
+    }
 }
diff --git a/tests/lib/SimpleSAML/Locale/LanguageTest.php b/tests/lib/SimpleSAML/Locale/LanguageTest.php
index f309bfb7501957cddc0efa4416a050ccbc4f8f66..c82cd1ffdd88a215014c908804cf974458055cef 100644
--- a/tests/lib/SimpleSAML/Locale/LanguageTest.php
+++ b/tests/lib/SimpleSAML/Locale/LanguageTest.php
@@ -173,4 +173,27 @@ class LanguageTest extends TestCase
         $l = new Language($c);
         $this->assertEquals('en', $l->getLanguage());
     }
+
+    public function testGetPreferredLanguages(): void
+    {
+        // test defaults
+        $c = Configuration::loadFromArray([], '', 'simplesaml');
+        $l = new Language($c);
+        $l->setLanguage('en');
+        $this->assertEquals(['en'], $l->getPreferredLanguages());
+
+        // test order current, default, fallback
+        $c = Configuration::loadFromArray([
+            'language.available' => ['fr', 'nn', 'es'],
+            'language.default' => 'nn',
+        ], '', 'simplesaml');
+        $l = new Language($c);
+        $l->setLanguage('es');
+        $this->assertEquals(['es', 'nn', 'en'], $l->getPreferredLanguages());
+
+        // test duplicate values (curlang is default lang) removed
+        $l->setLanguage('nn');
+        $this->assertEquals([0 => 'nn', 2 => 'en'], $l->getPreferredLanguages());
+    }
+
 }
diff --git a/tests/lib/SimpleSAML/XHTML/TemplateTest.php b/tests/lib/SimpleSAML/XHTML/TemplateTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d5c2732d1035b15784d8535af790e80641034ef5
--- /dev/null
+++ b/tests/lib/SimpleSAML/XHTML/TemplateTest.php
@@ -0,0 +1,114 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Test\XHTML;
+
+use Exception;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Configuration;
+use SimpleSAML\XHTML\Template;
+
+/**
+ * @covers \SimpleSAML\XHTML\Template
+ */
+class TemplateTest extends TestCase
+{
+    private const TEMPLATE = 'sandbox.twig';
+
+    function testSetup(): void
+    {
+        $c = Configuration::loadFromArray([], '', 'simplesaml');
+        $t = new Template($c, self::TEMPLATE);
+        $this->assertEquals(self::TEMPLATE, $t->getTemplateName());
+    }
+
+    function testNormalizeName(): void
+    {
+        $c = Configuration::loadFromArray([], '', 'simplesaml');
+        $t = new Template($c, 'sandbox');
+        $this->assertEquals(self::TEMPLATE, $t->getTemplateName());
+    }
+
+    function testTemplateModuleNamespace(): void
+    {
+        $c = Configuration::loadFromArray([], '', 'simplesaml');
+        $t = new Template($c, 'core:login');
+        $this->assertEquals('core:login.twig', $t->getTemplateName());
+    }
+
+    function testGetEntityDisplayNameBasic(): void
+    {
+        $c = Configuration::loadFromArray([], '', 'simplesaml');
+        $t = new Template($c, self::TEMPLATE);
+
+        $data = [
+            'entityid' => 'urn:example.org',
+            'name' => ['nl' => 'Something', 'en' => 'Other lang'],
+        ];
+        $name = $t->getEntityDisplayName($data);
+        $this->assertEquals('Other lang', $name);
+
+        $c = Configuration::loadFromArray(['language.default' => 'nl'], '', 'simplesaml');
+        $t = new Template($c, self::TEMPLATE);
+        $name = $t->getEntityDisplayName($data);
+        $this->assertEquals('Something', $name);
+    }
+
+    function testGetEntityDisplayNamePriorities(): void
+    {
+        $c = Configuration::loadFromArray([], '', 'simplesaml');
+        $t = new Template($c, self::TEMPLATE);
+
+        $data = [
+            'entityid' => 'urn:example.org',
+        ];
+        $name = $t->getEntityDisplayName($data);
+        $this->assertEquals('urn:example.org', $name);
+
+        $data['OrganizationName'] = ['fr' => 'Example Org', 'nl' => 'Anything Org'];
+        $data['OrganizationDisplayName'] = ['fr' => 'DisplayExample', 'nl' => 'DisplayAnything'];
+
+        $name = $t->getEntityDisplayName($data);
+        $this->assertEquals('urn:example.org', $name);
+
+        $data['OrganizationName']['en'] = 'Example Org EN';
+        
+        $name = $t->getEntityDisplayName($data);
+        $this->assertEquals('Example Org EN', $name);
+
+        $c = Configuration::loadFromArray(['language.default' => 'nl'], '', 'simplesaml');
+        $t = new Template($c, self::TEMPLATE);
+
+        $data['UIInfo']['DisplayName'] = ['de' => 'UIname', 'nl' => 'UIname NL'];
+        $name = $t->getEntityDisplayName($data);
+        $this->assertEquals('UIname NL', $name);
+    }
+
+    function testGetEntityPropertyTranslation(): void
+    {
+        $c = Configuration::loadFromArray([], '', 'simplesaml');
+        $t = new Template($c, self::TEMPLATE);
+
+	$prop = 'description';
+        $data = [
+            'entityid' => 'urn:example.org',
+            $prop => ['nl' => 'Something', 'en' => 'Other lang', 'fr' => 'Another desc'],
+        ];
+        $name = $t->getEntityPropertyTranslation($prop, $data);
+        $this->assertEquals('Other lang', $name);
+
+        $c = Configuration::loadFromArray(['language.default' => 'nl'], '', 'simplesaml');
+        $t = new Template($c, self::TEMPLATE);
+        $name = $t->getEntityPropertyTranslation($prop, $data);
+        $this->assertEquals('Something', $name);
+
+	unset($data[$prop]['nl']);
+        $name = $t->getEntityPropertyTranslation($prop, $data);
+        $this->assertEquals('Other lang', $name);
+
+	unset($data[$prop]['en']);
+        $name = $t->getEntityPropertyTranslation($prop, $data);
+        $this->assertNull($name);
+    }
+}