/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <sal/config.h>

#include <cppunit/TestFixture.h>
#include <cppunit/TestAssert.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/plugin/TestPlugIn.h>

#include <i18nlangtag/mslangid.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <i18nlangtag/languagetagicu.hxx>

#include <rtl/ustring.hxx>
#include <rtl/ustrbuf.hxx>

#include <com/sun/star/lang/Locale.hpp>
#include <com/sun/star/i18n/ScriptType.hpp>

using namespace com::sun::star;

namespace {

class TestLanguageTag : public CppUnit::TestFixture
{
public:
    TestLanguageTag() {}

    void testAllTags();
    void testAllIsoLangEntries();
    void testDisplayNames();
    void testLanguagesWithoutHyphenation();
    void testLongBcp47ToStackString();
    void testLongLanguageToStackString();
    void testLongLanguageCountryToStackString();

    CPPUNIT_TEST_SUITE(TestLanguageTag);
    CPPUNIT_TEST(testAllTags);
    CPPUNIT_TEST(testAllIsoLangEntries);
    CPPUNIT_TEST(testDisplayNames);
    CPPUNIT_TEST(testLanguagesWithoutHyphenation);
    CPPUNIT_TEST(testLongBcp47ToStackString);
    CPPUNIT_TEST(testLongLanguageToStackString);
    CPPUNIT_TEST(testLongLanguageCountryToStackString);
    CPPUNIT_TEST_SUITE_END();
};

void TestLanguageTag::testAllTags()
{
    {
        LanguageTag de_DE( u"de-Latn-DE"_ustr, true );
        OUString aBcp47 = de_DE.getBcp47();
        lang::Locale aLocale = de_DE.getLocale();
        LanguageType nLanguageType = de_DE.getLanguageType();
        CPPUNIT_ASSERT_EQUAL_MESSAGE("Default script should be stripped after canonicalize.", u"de-DE"_ustr, aBcp47 );
        CPPUNIT_ASSERT_EQUAL( u"de"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"DE"_ustr, aLocale.Country );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_GERMAN, nLanguageType );
        CPPUNIT_ASSERT_EQUAL( u"de"_ustr, de_DE.getLanguage() );
        CPPUNIT_ASSERT_EQUAL( u"DE"_ustr, de_DE.getCountry() );
        CPPUNIT_ASSERT( de_DE.getScript().isEmpty() );
        CPPUNIT_ASSERT_EQUAL( u"de"_ustr, de_DE.getLanguageAndScript() );
        CPPUNIT_ASSERT_EQUAL( u"de-DE"_ustr, de_DE.makeFallback().getBcp47() );
    }

    {
        LanguageTag klingon( u"i-klingon"_ustr, true );
        lang::Locale aLocale = klingon.getLocale();
        CPPUNIT_ASSERT_EQUAL( u"tlh"_ustr, klingon.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"tlh"_ustr, aLocale.Language );
        CPPUNIT_ASSERT( aLocale.Country.isEmpty() );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT( klingon.isValidBcp47() );
        CPPUNIT_ASSERT( klingon.isIsoLocale() );
        CPPUNIT_ASSERT( klingon.isIsoODF() );
        LanguageType nLang = klingon.getLanguageType();
        LanguageTag klingon_id( nLang);
        CPPUNIT_ASSERT_EQUAL( u"tlh"_ustr, klingon_id.getBcp47() );
    }

    {
        OUString s_sr_RS( u"sr-RS"_ustr );
        LanguageTag sr_RS( s_sr_RS, true );
        lang::Locale aLocale = sr_RS.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_sr_RS, sr_RS.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"sr"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"RS"_ustr, aLocale.Country );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_USER_SERBIAN_CYRILLIC_SERBIA, sr_RS.getLanguageType() );
        CPPUNIT_ASSERT( sr_RS.isValidBcp47() );
        CPPUNIT_ASSERT( sr_RS.isIsoLocale() );
        CPPUNIT_ASSERT( sr_RS.isIsoODF() );
    }

    {
        OUString s_sr_Latn_RS( u"sr-Latn-RS"_ustr );
        LanguageTag sr_RS( s_sr_Latn_RS, true );
        lang::Locale aLocale = sr_RS.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_sr_Latn_RS, sr_RS.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qlt"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"RS"_ustr, aLocale.Country );
        CPPUNIT_ASSERT_EQUAL( s_sr_Latn_RS, aLocale.Variant );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_USER_SERBIAN_LATIN_SERBIA, sr_RS.getLanguageType() );
        CPPUNIT_ASSERT( sr_RS.isValidBcp47() );
        CPPUNIT_ASSERT( !sr_RS.isIsoLocale() );
        CPPUNIT_ASSERT( sr_RS.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"sr"_ustr, sr_RS.getLanguage() );
        CPPUNIT_ASSERT_EQUAL( u"RS"_ustr, sr_RS.getCountry() );
        CPPUNIT_ASSERT_EQUAL( u"Latn"_ustr, sr_RS.getScript() );
        CPPUNIT_ASSERT_EQUAL( u"sr-Latn"_ustr, sr_RS.getLanguageAndScript() );
    }

    {
        OUString s_sr_Latn_CS( u"sr-Latn-CS"_ustr );
        LanguageTag sr_Latn_CS( s_sr_Latn_CS, true );
        lang::Locale aLocale = sr_Latn_CS.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_sr_Latn_CS, sr_Latn_CS.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qlt"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"CS"_ustr, aLocale.Country );
        CPPUNIT_ASSERT_EQUAL( s_sr_Latn_CS, aLocale.Variant );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_SERBIAN_LATIN_SAM, sr_Latn_CS.getLanguageType() );
        CPPUNIT_ASSERT( sr_Latn_CS.isValidBcp47() );
        CPPUNIT_ASSERT( !sr_Latn_CS.isIsoLocale() );
        CPPUNIT_ASSERT( sr_Latn_CS.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"sr"_ustr, sr_Latn_CS.getLanguage() );
        CPPUNIT_ASSERT_EQUAL( u"CS"_ustr, sr_Latn_CS.getCountry() );
        CPPUNIT_ASSERT_EQUAL( u"Latn"_ustr, sr_Latn_CS.getScript() );
        CPPUNIT_ASSERT_EQUAL( u"sr-Latn"_ustr, sr_Latn_CS.getLanguageAndScript() );
        ::std::vector< OUString > sr_Latn_CS_Fallbacks( sr_Latn_CS.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(9), sr_Latn_CS_Fallbacks.size());
        CPPUNIT_ASSERT_EQUAL( u"sr-Latn-CS"_ustr, sr_Latn_CS_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"sr-Latn-YU"_ustr, sr_Latn_CS_Fallbacks[1]);
        CPPUNIT_ASSERT_EQUAL( u"sh-CS"_ustr, sr_Latn_CS_Fallbacks[2]);
        CPPUNIT_ASSERT_EQUAL( u"sh-YU"_ustr, sr_Latn_CS_Fallbacks[3]);
        CPPUNIT_ASSERT_EQUAL( u"sr-Latn"_ustr, sr_Latn_CS_Fallbacks[4]);
        CPPUNIT_ASSERT_EQUAL( u"sh"_ustr, sr_Latn_CS_Fallbacks[5]);
        CPPUNIT_ASSERT_EQUAL( u"sr-CS"_ustr, sr_Latn_CS_Fallbacks[6]);
        CPPUNIT_ASSERT_EQUAL( u"sr-YU"_ustr, sr_Latn_CS_Fallbacks[7]);
        CPPUNIT_ASSERT_EQUAL( u"sr"_ustr, sr_Latn_CS_Fallbacks[8]);
        CPPUNIT_ASSERT_EQUAL( u"sr-Latn-CS"_ustr, sr_Latn_CS.makeFallback().getBcp47());
    }

    // 'sh-RS' has an internal override to 'sr-Latn-RS'
    {
        LanguageTag sh_RS( u"sh-RS"_ustr, true );
        lang::Locale aLocale = sh_RS.getLocale();
        CPPUNIT_ASSERT_EQUAL( u"sr-Latn-RS"_ustr, sh_RS.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( I18NLANGTAG_QLT , aLocale.Language);
        CPPUNIT_ASSERT_EQUAL( u"RS"_ustr, aLocale.Country );
        CPPUNIT_ASSERT_EQUAL( u"sr-Latn-RS"_ustr, aLocale.Variant );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_USER_SERBIAN_LATIN_SERBIA, sh_RS.getLanguageType() );
        CPPUNIT_ASSERT( sh_RS.isValidBcp47() );
        CPPUNIT_ASSERT( !sh_RS.isIsoLocale() );
        CPPUNIT_ASSERT( sh_RS.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"sr"_ustr, sh_RS.getLanguage() );
        CPPUNIT_ASSERT_EQUAL( u"RS"_ustr, sh_RS.getCountry() );
        CPPUNIT_ASSERT_EQUAL( u"Latn"_ustr, sh_RS.getScript() );
        CPPUNIT_ASSERT_EQUAL( u"sr-Latn"_ustr, sh_RS.getLanguageAndScript() );
        ::std::vector< OUString > sh_RS_Fallbacks( sh_RS.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(6), sh_RS_Fallbacks.size());
        CPPUNIT_ASSERT_EQUAL( u"sr-Latn-RS"_ustr, sh_RS_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"sh-RS"_ustr, sh_RS_Fallbacks[1]);
        CPPUNIT_ASSERT_EQUAL( u"sr-Latn"_ustr, sh_RS_Fallbacks[2]);
        CPPUNIT_ASSERT_EQUAL( u"sh"_ustr, sh_RS_Fallbacks[3]);
        CPPUNIT_ASSERT_EQUAL( u"sr-RS"_ustr, sh_RS_Fallbacks[4]);
        CPPUNIT_ASSERT_EQUAL( u"sr"_ustr, sh_RS_Fallbacks[5]);
        CPPUNIT_ASSERT_EQUAL( u"sr-Latn-RS"_ustr, sh_RS.makeFallback().getBcp47());
        CPPUNIT_ASSERT_EQUAL( u"sr-Latn-RS"_ustr, sh_RS.getBcp47());
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_USER_SERBIAN_LATIN_SERBIA, sh_RS.getLanguageType() );
    }

    // 'bs-Latn-BA' with 'Latn' suppress-script, we map that ourselves for a
    // known LangID with an override and canonicalization should work the same
    // without liblangtag.
    {
        LanguageTag bs_Latn_BA( u"bs-Latn-BA"_ustr, true );
        lang::Locale aLocale = bs_Latn_BA.getLocale();
        CPPUNIT_ASSERT_EQUAL( u"bs-BA"_ustr, bs_Latn_BA.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"bs"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"BA"_ustr, aLocale.Country );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_BOSNIAN_LATIN_BOSNIA_HERZEGOVINA, bs_Latn_BA.getLanguageType() );
        CPPUNIT_ASSERT( bs_Latn_BA.isValidBcp47() );
        CPPUNIT_ASSERT( bs_Latn_BA.isIsoLocale() );
        CPPUNIT_ASSERT( bs_Latn_BA.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"bs"_ustr, bs_Latn_BA.getLanguage() );
        CPPUNIT_ASSERT_EQUAL( u"BA"_ustr, bs_Latn_BA.getCountry() );
        CPPUNIT_ASSERT( bs_Latn_BA.getScript().isEmpty() );
        CPPUNIT_ASSERT_EQUAL( u"bs"_ustr, bs_Latn_BA.getLanguageAndScript() );
        ::std::vector< OUString > bs_Latn_BA_Fallbacks( bs_Latn_BA.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(2), bs_Latn_BA_Fallbacks.size());
        CPPUNIT_ASSERT_EQUAL( u"bs-BA"_ustr, bs_Latn_BA_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"bs"_ustr, bs_Latn_BA_Fallbacks[1]);
        CPPUNIT_ASSERT_EQUAL( u"bs-BA"_ustr, bs_Latn_BA.makeFallback().getBcp47());
        CPPUNIT_ASSERT_EQUAL( u"bs-BA"_ustr, bs_Latn_BA.getBcp47());
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_BOSNIAN_LATIN_BOSNIA_HERZEGOVINA, bs_Latn_BA.getLanguageType() );
    }

    {
        OUString s_ca_ES_valencia( u"ca-ES-valencia"_ustr );
        LanguageTag ca_ES_valencia( s_ca_ES_valencia, true );
        lang::Locale aLocale = ca_ES_valencia.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_ca_ES_valencia, ca_ES_valencia.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qlt"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"ES"_ustr, aLocale.Country );
        CPPUNIT_ASSERT_EQUAL( s_ca_ES_valencia, aLocale.Variant );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_CATALAN_VALENCIAN, ca_ES_valencia.getLanguageType() );
        CPPUNIT_ASSERT( ca_ES_valencia.isValidBcp47() );
        CPPUNIT_ASSERT( !ca_ES_valencia.isIsoLocale() );
        CPPUNIT_ASSERT( !ca_ES_valencia.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"ca"_ustr, ca_ES_valencia.getLanguage() );
        CPPUNIT_ASSERT_EQUAL( u"ES"_ustr, ca_ES_valencia.getCountry() );
        CPPUNIT_ASSERT( ca_ES_valencia.getScript().isEmpty() );
        CPPUNIT_ASSERT_EQUAL( u"ca"_ustr, ca_ES_valencia.getLanguageAndScript() );
        ::std::vector< OUString > ca_ES_valencia_Fallbacks( ca_ES_valencia.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(5), ca_ES_valencia_Fallbacks.size());
        CPPUNIT_ASSERT_EQUAL( u"ca-ES-valencia"_ustr, ca_ES_valencia_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"ca-XV"_ustr, ca_ES_valencia_Fallbacks[1]);
        CPPUNIT_ASSERT_EQUAL( u"ca-valencia"_ustr, ca_ES_valencia_Fallbacks[2]);
        CPPUNIT_ASSERT_EQUAL( u"ca-ES"_ustr, ca_ES_valencia_Fallbacks[3]);
        CPPUNIT_ASSERT_EQUAL( u"ca"_ustr, ca_ES_valencia_Fallbacks[4]);
        CPPUNIT_ASSERT_EQUAL( u"ca-ES-valencia"_ustr, ca_ES_valencia.makeFallback().getBcp47());
    }

    {
        OUString s_ca_valencia( u"ca-valencia"_ustr );
        LanguageTag ca_valencia( s_ca_valencia, true );
        lang::Locale aLocale = ca_valencia.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_ca_valencia, ca_valencia.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qlt"_ustr, aLocale.Language );
        CPPUNIT_ASSERT( aLocale.Country.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( s_ca_valencia, aLocale.Variant );
        CPPUNIT_ASSERT( LanguageTag::isOnTheFlyID( ca_valencia.getLanguageType()) );
        CPPUNIT_ASSERT( ca_valencia.isValidBcp47() );
        CPPUNIT_ASSERT( !ca_valencia.isIsoLocale() );
        CPPUNIT_ASSERT( !ca_valencia.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"ca"_ustr, ca_valencia.getLanguage() );
        CPPUNIT_ASSERT( ca_valencia.getCountry().isEmpty() );
        CPPUNIT_ASSERT( ca_valencia.getScript().isEmpty() );
        CPPUNIT_ASSERT_EQUAL( u"ca"_ustr, ca_valencia.getLanguageAndScript() );
        ::std::vector< OUString > ca_valencia_Fallbacks( ca_valencia.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(2), ca_valencia_Fallbacks.size());
        CPPUNIT_ASSERT_EQUAL( u"ca-valencia"_ustr, ca_valencia_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"ca"_ustr, ca_valencia_Fallbacks[1]);
        CPPUNIT_ASSERT_EQUAL( u"ca-ES-valencia"_ustr, ca_valencia.makeFallback().getBcp47());
    }

    // 'ca-XV' has an internal override to 'ca-ES-valencia'
    {
        OUString s_ca_ES_valencia( u"ca-ES-valencia"_ustr );
        LanguageTag ca_XV( u"ca-XV"_ustr, true );
        lang::Locale aLocale = ca_XV.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_ca_ES_valencia, ca_XV.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( I18NLANGTAG_QLT , aLocale.Language);
        CPPUNIT_ASSERT_EQUAL( u"ES"_ustr, aLocale.Country );
        CPPUNIT_ASSERT_EQUAL( s_ca_ES_valencia, aLocale.Variant );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_CATALAN_VALENCIAN, ca_XV.getLanguageType() );
        CPPUNIT_ASSERT( ca_XV.isValidBcp47() );
        CPPUNIT_ASSERT( !ca_XV.isIsoLocale() );
        CPPUNIT_ASSERT( !ca_XV.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"ca"_ustr, ca_XV.getLanguage() );
        CPPUNIT_ASSERT_EQUAL( u"ES"_ustr, ca_XV.getCountry() );
        CPPUNIT_ASSERT( ca_XV.getScript().isEmpty() );
        CPPUNIT_ASSERT_EQUAL( u"ca"_ustr, ca_XV.getLanguageAndScript() );
        ::std::vector< OUString > ca_XV_Fallbacks( ca_XV.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(5), ca_XV_Fallbacks.size());
        CPPUNIT_ASSERT_EQUAL( u"ca-ES-valencia"_ustr, ca_XV_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"ca-XV"_ustr, ca_XV_Fallbacks[1]);
        CPPUNIT_ASSERT_EQUAL( u"ca-valencia"_ustr, ca_XV_Fallbacks[2]);
        CPPUNIT_ASSERT_EQUAL( u"ca-ES"_ustr, ca_XV_Fallbacks[3]);
        CPPUNIT_ASSERT_EQUAL( u"ca"_ustr, ca_XV_Fallbacks[4]);
        CPPUNIT_ASSERT_EQUAL( u"ca-ES-valencia"_ustr, ca_XV.makeFallback().getBcp47());
    }

    {
        OUString s_de_DE( u"de-DE"_ustr );
        LanguageTag de_DE( s_de_DE, true );
        lang::Locale aLocale = de_DE.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_de_DE, de_DE.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"de"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"DE"_ustr, aLocale.Country );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_GERMAN, de_DE.getLanguageType() );
        CPPUNIT_ASSERT( de_DE.isValidBcp47() );
        CPPUNIT_ASSERT( de_DE.isIsoLocale() );
        CPPUNIT_ASSERT( de_DE.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"de"_ustr, de_DE.getLanguage() );
        CPPUNIT_ASSERT_EQUAL( u"DE"_ustr, de_DE.getCountry() );
        CPPUNIT_ASSERT( de_DE.getScript().isEmpty() );
        CPPUNIT_ASSERT_EQUAL( u"de"_ustr, de_DE.getLanguageAndScript() );
        ::std::vector< OUString > de_DE_Fallbacks( de_DE.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(2), de_DE_Fallbacks.size());
        CPPUNIT_ASSERT_EQUAL( u"de-DE"_ustr, de_DE_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"de"_ustr, de_DE_Fallbacks[1]);
        CPPUNIT_ASSERT_EQUAL( u"de-DE"_ustr, de_DE.makeFallback().getBcp47());
    }

    {
        LanguageTag de_DE( lang::Locale( u"de"_ustr, u"DE"_ustr, u""_ustr ) );
        lang::Locale aLocale = de_DE.getLocale();
        CPPUNIT_ASSERT_EQUAL( u"de-DE"_ustr, de_DE.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"de"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"DE"_ustr, aLocale.Country );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_GERMAN, de_DE.getLanguageType() );
    }

    {
        LanguageTag de_DE( LANGUAGE_GERMAN );
        lang::Locale aLocale = de_DE.getLocale();
        CPPUNIT_ASSERT_EQUAL( u"de-DE"_ustr, de_DE.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"de"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"DE"_ustr, aLocale.Country );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_GERMAN, de_DE.getLanguageType() );
    }

    // Unmapped but known language-only.
    {
        OUString s_de( u"de"_ustr );
        LanguageTag de( s_de, true );
        lang::Locale aLocale = de.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_de, de.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"de"_ustr, aLocale.Language );
        CPPUNIT_ASSERT( aLocale.Country.isEmpty() );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        LanguageType de_LangID = de.getLanguageType();
        CPPUNIT_ASSERT( de_LangID != LANGUAGE_GERMAN );
        CPPUNIT_ASSERT_EQUAL( MsLangId::getPrimaryLanguage( LANGUAGE_GERMAN) , de_LangID);
        CPPUNIT_ASSERT_EQUAL( u"de-DE"_ustr, de.makeFallback().getBcp47());
        // Check registered mapping.
        LanguageTag de_l( de_LangID);
        CPPUNIT_ASSERT_EQUAL( s_de, de_l.getBcp47() );
    }

    // "bo" and "dz" share the same primary language ID, only one gets it
    // assigned, "dz" language-only has a special mapping.
    {
        LanguageTag bo( u"bo"_ustr, true );
        CPPUNIT_ASSERT_EQUAL( MsLangId::getPrimaryLanguage( LANGUAGE_TIBETAN), bo.getLanguageType() );
        LanguageTag dz( u"dz"_ustr, true );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_USER_DZONGKHA_MAP_LONLY, dz.getLanguageType() );
    }

    // "no", "nb" and "nn" share the same primary language ID, which even is
    // assigned to "no-NO" for legacy so none gets it assigned, all on-the-fly
    // except if there is a defined MS-LCID for LanguageScriptOnly (LSO).
    {
        LanguageTag no( u"no"_ustr, true );
        CPPUNIT_ASSERT( LanguageTag::isOnTheFlyID( no.getLanguageType()) );
        LanguageTag nb( u"nb"_ustr, true );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_NORWEGIAN_BOKMAL_LSO, nb.getLanguageType() );
        LanguageTag nn( u"nn"_ustr, true );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_NORWEGIAN_NYNORSK_LSO, nn.getLanguageType() );
        LanguageTag no_NO( u"no-NO"_ustr, true );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_NORWEGIAN, no_NO.getLanguageType() );
    }

    // 'de-1901' derived from 'de-DE-1901' grandfathered to check that it is
    // accepted as (DIGIT 3ALNUM) variant
    {
        OUString s_de_1901( u"de-1901"_ustr );
        LanguageTag de_1901( s_de_1901 );
        lang::Locale aLocale = de_1901.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_de_1901, de_1901.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qlt"_ustr, aLocale.Language );
        CPPUNIT_ASSERT( aLocale.Country.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( s_de_1901, aLocale.Variant );
        CPPUNIT_ASSERT( LanguageTag::isOnTheFlyID( de_1901.getLanguageType()) );
        CPPUNIT_ASSERT( de_1901.isValidBcp47() );
        CPPUNIT_ASSERT( !de_1901.isIsoLocale() );
        CPPUNIT_ASSERT( !de_1901.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"de"_ustr, de_1901.getLanguageAndScript() );
        CPPUNIT_ASSERT_EQUAL( u"1901"_ustr, de_1901.getVariants() );
        ::std::vector< OUString > de_1901_Fallbacks( de_1901.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(2), de_1901_Fallbacks.size());
        CPPUNIT_ASSERT_EQUAL( u"de-1901"_ustr, de_1901_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"de"_ustr, de_1901_Fallbacks[1]);
    }

    // 'en-GB-oed' is known grandfathered for English, Oxford English
    // Dictionary spelling.
    // Deprecated as of 2015-04-17, prefer en-GB-oxendict instead.
    // As of 2017-03-14 we also alias to en-GB-oxendict.
    {
        OUString s_en_GB_oxendict( u"en-GB-oxendict"_ustr );
        LanguageTag en_GB_oed( u"en-GB-oed"_ustr );
        lang::Locale aLocale = en_GB_oed.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_en_GB_oxendict, en_GB_oed.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qlt"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"GB"_ustr, aLocale.Country );
        CPPUNIT_ASSERT_EQUAL( s_en_GB_oxendict, aLocale.Variant );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_USER_ENGLISH_UK_OXENDICT, en_GB_oed.getLanguageType() );
        CPPUNIT_ASSERT( en_GB_oed.isValidBcp47() );
        CPPUNIT_ASSERT( !en_GB_oed.isIsoLocale() );
        CPPUNIT_ASSERT( !en_GB_oed.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"en"_ustr, en_GB_oed.getLanguageAndScript() );
        CPPUNIT_ASSERT_EQUAL( u"oxendict"_ustr, en_GB_oed.getVariants() );
        ::std::vector< OUString > en_GB_oed_Fallbacks( en_GB_oed.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(5), en_GB_oed_Fallbacks.size() );
        CPPUNIT_ASSERT_EQUAL( u"en-GB-oxendict"_ustr, en_GB_oed_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"en-GB-oed"_ustr, en_GB_oed_Fallbacks[1]);
        CPPUNIT_ASSERT_EQUAL( u"en-oxendict"_ustr, en_GB_oed_Fallbacks[2]);
        CPPUNIT_ASSERT_EQUAL( u"en-GB"_ustr, en_GB_oed_Fallbacks[3]);
        CPPUNIT_ASSERT_EQUAL( u"en"_ustr, en_GB_oed_Fallbacks[4]);
        // 'en-oed' is not a valid fallback!
    }

    // 'en-GB-oxendict' as preferred over 'en-GB-oed'.
    {
        OUString s_en_GB_oxendict( u"en-GB-oxendict"_ustr );
        LanguageTag en_GB_oxendict( s_en_GB_oxendict );
        lang::Locale aLocale = en_GB_oxendict.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_en_GB_oxendict, en_GB_oxendict.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qlt"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"GB"_ustr, aLocale.Country );
        CPPUNIT_ASSERT_EQUAL( s_en_GB_oxendict, aLocale.Variant );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_USER_ENGLISH_UK_OXENDICT, en_GB_oxendict.getLanguageType() );
        CPPUNIT_ASSERT( en_GB_oxendict.isValidBcp47() );
        CPPUNIT_ASSERT( !en_GB_oxendict.isIsoLocale() );
        CPPUNIT_ASSERT( !en_GB_oxendict.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"en"_ustr, en_GB_oxendict.getLanguageAndScript() );
        CPPUNIT_ASSERT_EQUAL( u"oxendict"_ustr, en_GB_oxendict.getVariants() );
        ::std::vector< OUString > en_GB_oxendict_Fallbacks( en_GB_oxendict.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(5), en_GB_oxendict_Fallbacks.size() );
        CPPUNIT_ASSERT_EQUAL( u"en-GB-oxendict"_ustr, en_GB_oxendict_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"en-GB-oed"_ustr, en_GB_oxendict_Fallbacks[1]);
        CPPUNIT_ASSERT_EQUAL( u"en-oxendict"_ustr, en_GB_oxendict_Fallbacks[2]);
        CPPUNIT_ASSERT_EQUAL( u"en-GB"_ustr, en_GB_oxendict_Fallbacks[3]);
        CPPUNIT_ASSERT_EQUAL( u"en"_ustr, en_GB_oxendict_Fallbacks[4]);
    }

    // 'es-ES-u-co-trad' is a valid (and known) Extension U tag
    {
        OUString s_es_ES_u_co_trad( u"es-ES-u-co-trad"_ustr );
        LanguageTag es_ES_u_co_trad( s_es_ES_u_co_trad );
        lang::Locale aLocale = es_ES_u_co_trad.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_es_ES_u_co_trad, es_ES_u_co_trad.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qlt"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"ES"_ustr, aLocale.Country );
        CPPUNIT_ASSERT_EQUAL( s_es_ES_u_co_trad, aLocale.Variant );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_SPANISH_DATED, es_ES_u_co_trad.getLanguageType() );
        CPPUNIT_ASSERT( es_ES_u_co_trad.isValidBcp47() );
        CPPUNIT_ASSERT( !es_ES_u_co_trad.isIsoLocale() );
        CPPUNIT_ASSERT( !es_ES_u_co_trad.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"es"_ustr, es_ES_u_co_trad.getLanguageAndScript() );
        CPPUNIT_ASSERT_EQUAL( u"u-co-trad"_ustr, es_ES_u_co_trad.getVariants() );
        ::std::vector< OUString > es_ES_u_co_trad_Fallbacks( es_ES_u_co_trad.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(4), es_ES_u_co_trad_Fallbacks.size() );
        CPPUNIT_ASSERT_EQUAL( u"es-ES-u-co-trad"_ustr, es_ES_u_co_trad_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"es-u-co-trad"_ustr, es_ES_u_co_trad_Fallbacks[1]);
        CPPUNIT_ASSERT_EQUAL( u"es-ES"_ustr, es_ES_u_co_trad_Fallbacks[2]);
        CPPUNIT_ASSERT_EQUAL( u"es"_ustr, es_ES_u_co_trad_Fallbacks[3]);
        // Map to broken MS.
        CPPUNIT_ASSERT_EQUAL( u"es-ES_tradnl"_ustr, es_ES_u_co_trad.getBcp47MS() );
    }

    // 'es-ES_tradnl' (broken MS) maps to 'es-ES-u-co-trad'
    {
        OUString s_es_ES_u_co_trad( u"es-ES-u-co-trad"_ustr );
        OUString s_es_ES_tradnl( u"es-ES_tradnl"_ustr );
        LanguageTag es_ES_tradnl( s_es_ES_tradnl );
        lang::Locale aLocale = es_ES_tradnl.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_es_ES_u_co_trad, es_ES_tradnl.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qlt"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"ES"_ustr, aLocale.Country );
        CPPUNIT_ASSERT_EQUAL( s_es_ES_u_co_trad, aLocale.Variant );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_SPANISH_DATED, es_ES_tradnl.getLanguageType() );
        CPPUNIT_ASSERT( es_ES_tradnl.isValidBcp47() );
        CPPUNIT_ASSERT( !es_ES_tradnl.isIsoLocale() );
        CPPUNIT_ASSERT( !es_ES_tradnl.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"es"_ustr, es_ES_tradnl.getLanguageAndScript() );
        CPPUNIT_ASSERT_EQUAL( u"u-co-trad"_ustr, es_ES_tradnl.getVariants() );
        ::std::vector< OUString > es_ES_tradnl_Fallbacks( es_ES_tradnl.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(4), es_ES_tradnl_Fallbacks.size() );
        CPPUNIT_ASSERT_EQUAL( u"es-ES-u-co-trad"_ustr, es_ES_tradnl_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"es-u-co-trad"_ustr, es_ES_tradnl_Fallbacks[1]);
        CPPUNIT_ASSERT_EQUAL( u"es-ES"_ustr, es_ES_tradnl_Fallbacks[2]);
        CPPUNIT_ASSERT_EQUAL( u"es"_ustr, es_ES_tradnl_Fallbacks[3]);
        // Map back to broken MS.
        CPPUNIT_ASSERT_EQUAL( s_es_ES_tradnl, es_ES_tradnl.getBcp47MS() );
    }

    // 'zh-Hans-CN' maps to 'zh-CN' and fallbacks
    {
        LanguageTag zh_Hans_CN( u"zh-Hans-CN"_ustr, true );
        lang::Locale aLocale = zh_Hans_CN.getLocale();
        CPPUNIT_ASSERT_EQUAL( u"zh-CN"_ustr, zh_Hans_CN.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"zh"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"CN"_ustr, aLocale.Country );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_CHINESE_SIMPLIFIED, zh_Hans_CN.getLanguageType() );
        CPPUNIT_ASSERT( zh_Hans_CN.isValidBcp47() );
        CPPUNIT_ASSERT( zh_Hans_CN.isIsoLocale() );
        CPPUNIT_ASSERT( zh_Hans_CN.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"zh"_ustr, zh_Hans_CN.getLanguage() );
        CPPUNIT_ASSERT_EQUAL( u"CN"_ustr, zh_Hans_CN.getCountry() );
        CPPUNIT_ASSERT( zh_Hans_CN.getScript().isEmpty() );
        CPPUNIT_ASSERT_EQUAL( u"zh"_ustr, zh_Hans_CN.getLanguageAndScript() );
        ::std::vector< OUString > zh_Hans_CN_Fallbacks( zh_Hans_CN.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(2), zh_Hans_CN_Fallbacks.size());
        CPPUNIT_ASSERT_EQUAL( u"zh-CN"_ustr, zh_Hans_CN_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"zh"_ustr, zh_Hans_CN_Fallbacks[1]);
        CPPUNIT_ASSERT_EQUAL( u"zh-CN"_ustr, zh_Hans_CN.makeFallback().getBcp47());
    }

    // 'zh-Hant-TW' maps to 'zh-TW' and fallbacks
    {
        LanguageTag zh_Hant_TW( u"zh-Hant-TW"_ustr, true );
        lang::Locale aLocale = zh_Hant_TW.getLocale();
        CPPUNIT_ASSERT_EQUAL( u"zh-TW"_ustr, zh_Hant_TW.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"zh"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"TW"_ustr, aLocale.Country );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_CHINESE_TRADITIONAL, zh_Hant_TW.getLanguageType() );
        CPPUNIT_ASSERT( zh_Hant_TW.isValidBcp47() );
        CPPUNIT_ASSERT( zh_Hant_TW.isIsoLocale() );
        CPPUNIT_ASSERT( zh_Hant_TW.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"zh"_ustr, zh_Hant_TW.getLanguage() );
        CPPUNIT_ASSERT_EQUAL( u"TW"_ustr, zh_Hant_TW.getCountry() );
        CPPUNIT_ASSERT( zh_Hant_TW.getScript().isEmpty() );
        CPPUNIT_ASSERT_EQUAL( u"zh"_ustr, zh_Hant_TW.getLanguageAndScript() );
        ::std::vector< OUString > zh_Hant_TW_Fallbacks( zh_Hant_TW.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(3), zh_Hant_TW_Fallbacks.size());
        CPPUNIT_ASSERT_EQUAL( u"zh-TW"_ustr, zh_Hant_TW_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"zh-CN"_ustr, zh_Hant_TW_Fallbacks[1]);
        CPPUNIT_ASSERT_EQUAL( u"zh"_ustr, zh_Hant_TW_Fallbacks[2]);
        CPPUNIT_ASSERT_EQUAL( u"zh-TW"_ustr, zh_Hant_TW.makeFallback().getBcp47());
    }

    // 'zh-SG' and fallbacks
    {
        OUString s_zh_SG( u"zh-SG"_ustr );
        LanguageTag zh_SG( s_zh_SG, true );
        lang::Locale aLocale = zh_SG.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_zh_SG, zh_SG.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"zh"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"SG"_ustr, aLocale.Country );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_CHINESE_SINGAPORE, zh_SG.getLanguageType() );
        CPPUNIT_ASSERT( zh_SG.isValidBcp47() );
        CPPUNIT_ASSERT( zh_SG.isIsoLocale() );
        CPPUNIT_ASSERT( zh_SG.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"zh"_ustr, zh_SG.getLanguage() );
        CPPUNIT_ASSERT_EQUAL( u"SG"_ustr, zh_SG.getCountry() );
        CPPUNIT_ASSERT( zh_SG.getScript().isEmpty() );
        CPPUNIT_ASSERT_EQUAL( u"zh"_ustr, zh_SG.getLanguageAndScript() );
        ::std::vector< OUString > zh_SG_Fallbacks( zh_SG.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(3), zh_SG_Fallbacks.size());
        CPPUNIT_ASSERT_EQUAL( u"zh-SG"_ustr, zh_SG_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"zh-CN"_ustr, zh_SG_Fallbacks[1]);
        CPPUNIT_ASSERT_EQUAL( u"zh"_ustr, zh_SG_Fallbacks[2]);
        CPPUNIT_ASSERT_EQUAL( u"zh-SG"_ustr, zh_SG.makeFallback().getBcp47());
    }

    // 'zh-HK' and fallbacks
    {
        OUString s_zh_HK( u"zh-HK"_ustr );
        LanguageTag zh_HK( s_zh_HK, true );
        lang::Locale aLocale = zh_HK.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_zh_HK, zh_HK.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"zh"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"HK"_ustr, aLocale.Country );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_CHINESE_HONGKONG, zh_HK.getLanguageType() );
        CPPUNIT_ASSERT( zh_HK.isValidBcp47() );
        CPPUNIT_ASSERT( zh_HK.isIsoLocale() );
        CPPUNIT_ASSERT( zh_HK.isIsoODF() );
        CPPUNIT_ASSERT_EQUAL( u"zh"_ustr, zh_HK.getLanguage() );
        CPPUNIT_ASSERT_EQUAL( u"HK"_ustr, zh_HK.getCountry() );
        CPPUNIT_ASSERT( zh_HK.getScript().isEmpty() );
        CPPUNIT_ASSERT_EQUAL( u"zh"_ustr, zh_HK.getLanguageAndScript() );
        ::std::vector< OUString > zh_HK_Fallbacks( zh_HK.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(3), zh_HK_Fallbacks.size());
        CPPUNIT_ASSERT_EQUAL( u"zh-HK"_ustr, zh_HK_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"zh-TW"_ustr, zh_HK_Fallbacks[1]);
        CPPUNIT_ASSERT_EQUAL( u"zh"_ustr, zh_HK_Fallbacks[2]);
        CPPUNIT_ASSERT_EQUAL( u"zh-HK"_ustr, zh_HK.makeFallback().getBcp47());
    }

    // 'zh-yue-HK' uses redundant 'zh-yue' and should be preferred 'yue-HK'
#if 0
    /* XXX Disabled because liblangtag in lt_tag_canonicalize() after replacing
     * 'zh-yue' with the preferred 'yue' does:
     * "If the language tag starts with a primary language subtag that is also
     * an extlang subtag, then the language tag is prepended with the extlang's
     * 'Prefix'."
     * Primary language 'yue' is also extlang 'yue' for which the prefix
     * happens to be 'zh' ... so the result is 'zh-yue-HK' again. */
    {
        OUString s_zh_yue_HK( "zh-yue-HK" );
        LanguageTag zh_yue_HK( s_zh_yue_HK );
        lang::Locale aLocale = zh_yue_HK.getLocale();
        CPPUNIT_ASSERT( zh_yue_HK.getBcp47() == "yue-HK" );
        CPPUNIT_ASSERT( aLocale.Language == "yue" );
        CPPUNIT_ASSERT( aLocale.Country == "HK" );
        CPPUNIT_ASSERT( aLocale.Variant == "" );
        CPPUNIT_ASSERT( zh_yue_HK.getLanguageType() == LANGUAGE_YUE_CHINESE_HONGKONG );
        CPPUNIT_ASSERT( zh_yue_HK.isValidBcp47() == true );
        CPPUNIT_ASSERT( zh_yue_HK.isIsoLocale() == true );
        CPPUNIT_ASSERT( zh_yue_HK.isIsoODF() == true );
        CPPUNIT_ASSERT( zh_yue_HK.getLanguageAndScript() == "yue" );
        CPPUNIT_ASSERT( zh_yue_HK.getVariants() == "" );
        ::std::vector< OUString > zh_yue_HK_Fallbacks( zh_yue_HK.getFallbackStrings( true));
        CPPUNIT_ASSERT( zh_yue_HK_Fallbacks.size() == 2);
        CPPUNIT_ASSERT( zh_yue_HK_Fallbacks[0] == "yue-HK");
        CPPUNIT_ASSERT( zh_yue_HK_Fallbacks[1] == "yue");
    }
#endif

    // 'es-419' fallbacks
    {
        OUString s_es_419( u"es-419"_ustr );
        LanguageTag es_419( s_es_419 );
        lang::Locale aLocale = es_419.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_es_419, es_419.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qlt"_ustr, aLocale.Language );
        CPPUNIT_ASSERT( aLocale.Country.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( u"es-419"_ustr, aLocale.Variant );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_SPANISH_LATIN_AMERICA, es_419.getLanguageType() );
        ::std::vector< OUString > es_419_Fallbacks( es_419.getFallbackStrings( true));
        CPPUNIT_ASSERT_EQUAL( static_cast<size_t>(2), es_419_Fallbacks.size());
        CPPUNIT_ASSERT_EQUAL( u"es-419"_ustr, es_419_Fallbacks[0]);
        CPPUNIT_ASSERT_EQUAL( u"es"_ustr, es_419_Fallbacks[1]);
    }

    // 'qtz' is a local use known pseudolocale for key ID resource
    {
        OUString s_qtz( u"qtz"_ustr );
        LanguageTag qtz( s_qtz );
        lang::Locale aLocale = qtz.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_qtz, qtz.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qtz"_ustr, aLocale.Language );
        CPPUNIT_ASSERT( aLocale.Country.isEmpty() );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_USER_KEYID, qtz.getLanguageType() );
    }

    // 'qty' is a local use unknown locale
    {
        OUString s_qty( u"qty"_ustr );
        LanguageTag qty( s_qty );
        lang::Locale aLocale = qty.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_qty, qty.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qty"_ustr, aLocale.Language );
        CPPUNIT_ASSERT( aLocale.Country.isEmpty() );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT( LanguageTag::isOnTheFlyID( qty.getLanguageType()) );
    }

    // 'qtx' is an unknown new mslangid
    {
        LanguageTag qtx( u"qtx"_ustr );
        qtx.setScriptType( LanguageTag::ScriptType::RTL );
        LanguageType n_qtx = qtx.getLanguageType();
        CPPUNIT_ASSERT_EQUAL( css::i18n::ScriptType::COMPLEX, MsLangId::getScriptType(n_qtx) );
        CPPUNIT_ASSERT( MsLangId::isRightToLeft(n_qtx) );
        CPPUNIT_ASSERT( !MsLangId::isCJK(n_qtx) );
    }

    // 'x-comment' is a privateuse known "locale"
    {
        OUString s_xcomment( u"x-comment"_ustr );
        LanguageTag xcomment( s_xcomment );
        lang::Locale aLocale = xcomment.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_xcomment, xcomment.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qlt"_ustr, aLocale.Language );
        CPPUNIT_ASSERT( aLocale.Country.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( u"x-comment"_ustr, aLocale.Variant );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_USER_PRIV_COMMENT, xcomment.getLanguageType() );
    }

    // 'x-foobar' is a privateuse unknown "locale"
    {
        OUString s_xfoobar( u"x-foobar"_ustr );
        LanguageTag xfoobar( s_xfoobar );
        lang::Locale aLocale = xfoobar.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_xfoobar, xfoobar.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qlt"_ustr, aLocale.Language );
        CPPUNIT_ASSERT( aLocale.Country.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( u"x-foobar"_ustr, aLocale.Variant );
        CPPUNIT_ASSERT( LanguageTag::isOnTheFlyID( xfoobar.getLanguageType()) );
    }

    // '*' the dreaded jolly joker is a "privateuse" known "locale"
    {
        OUString s_joker( u"*"_ustr );
        LanguageTag joker( s_joker );
        lang::Locale aLocale = joker.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_joker, joker.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qlt"_ustr, aLocale.Language );
        CPPUNIT_ASSERT( aLocale.Country.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( u"*"_ustr, aLocale.Variant );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_USER_PRIV_JOKER, joker.getLanguageType() );

        joker.reset( LANGUAGE_USER_PRIV_JOKER );
        aLocale = joker.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_joker, joker.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qlt"_ustr, aLocale.Language );
        CPPUNIT_ASSERT( aLocale.Country.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( u"*"_ustr, aLocale.Variant );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_USER_PRIV_JOKER, joker.getLanguageType() );
    }

    // 'C' locale shall map to 'en-US'
    {
        LanguageTag aTag( u"C"_ustr );
        CPPUNIT_ASSERT_EQUAL( u"en"_ustr, aTag.getLanguage() );
        CPPUNIT_ASSERT_EQUAL( u"US"_ustr, aTag.getCountry() );
        lang::Locale aLocale = aTag.getLocale();
        CPPUNIT_ASSERT_EQUAL( u"en-US"_ustr, aTag.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"en"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"US"_ustr, aLocale.Country );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_ENGLISH_US, aTag.getLanguageType() );
    }
    {
        LanguageTag aTag( lang::Locale(u"C"_ustr,u""_ustr,u""_ustr) );
        CPPUNIT_ASSERT_EQUAL( u"en"_ustr, aTag.getLanguage() );
        CPPUNIT_ASSERT_EQUAL( u"US"_ustr, aTag.getCountry() );
        lang::Locale aLocale = aTag.getLocale();
        CPPUNIT_ASSERT_EQUAL( u"en-US"_ustr, aTag.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"en"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"US"_ustr, aLocale.Country );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_ENGLISH_US, aTag.getLanguageType() );
    }

    // test reset() methods
    {
        LanguageTag aTag( LANGUAGE_DONTKNOW );
        lang::Locale aLocale;

        aTag.reset( LANGUAGE_GERMAN );
        aLocale = aTag.getLocale();
        CPPUNIT_ASSERT_EQUAL( u"de-DE"_ustr, aTag.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"de"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"DE"_ustr, aLocale.Country );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_GERMAN, aTag.getLanguageType() );

        aTag.reset( u"en-US"_ustr );
        aLocale = aTag.getLocale();
        CPPUNIT_ASSERT_EQUAL( u"en-US"_ustr, aTag.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"en"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"US"_ustr, aLocale.Country );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_ENGLISH_US, aTag.getLanguageType() );

        aTag.reset( lang::Locale( u"de"_ustr, u"DE"_ustr, u""_ustr ) );
        aLocale = aTag.getLocale();
        CPPUNIT_ASSERT_EQUAL( u"de-DE"_ustr, aTag.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"de"_ustr, aLocale.Language );
        CPPUNIT_ASSERT_EQUAL( u"DE"_ustr, aLocale.Country );
        CPPUNIT_ASSERT( aLocale.Variant.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_GERMAN, aTag.getLanguageType() );
    }

    {
        OUString s_uab( u"unreg-and-bad"_ustr );
        LanguageTag uab( s_uab, true );
        lang::Locale aLocale = uab.getLocale();
        CPPUNIT_ASSERT_EQUAL( s_uab, uab.getBcp47() );
        CPPUNIT_ASSERT_EQUAL( u"qlt"_ustr, aLocale.Language );
        CPPUNIT_ASSERT( aLocale.Country.isEmpty() );
        CPPUNIT_ASSERT_EQUAL( s_uab, aLocale.Variant );
        CPPUNIT_ASSERT_EQUAL( LANGUAGE_DONTKNOW, uab.getLanguageType() );
        CPPUNIT_ASSERT( !uab.isValidBcp47() );
        CPPUNIT_ASSERT( !uab.isIsoLocale() );
        CPPUNIT_ASSERT( !uab.isIsoODF() );
    }

    // test static isValidBcp47() method
    {
        OUString aCanonicalized;
        CPPUNIT_ASSERT( LanguageTag::isValidBcp47( u"en-US"_ustr, &aCanonicalized) );
        CPPUNIT_ASSERT_EQUAL( u"en-US"_ustr, aCanonicalized );
        CPPUNIT_ASSERT( LanguageTag::isValidBcp47( u"x-foobar"_ustr, &aCanonicalized) );
        CPPUNIT_ASSERT_EQUAL( u"x-foobar"_ustr, aCanonicalized );
        CPPUNIT_ASSERT( LanguageTag::isValidBcp47( u"qaa"_ustr, &aCanonicalized) );
        CPPUNIT_ASSERT_EQUAL( u"qaa"_ustr, aCanonicalized );
        CPPUNIT_ASSERT( !LanguageTag::isValidBcp47( u"unreg-and-bad"_ustr, &aCanonicalized) );
        CPPUNIT_ASSERT( LanguageTag::isValidBcp47( u"en-US"_ustr, &aCanonicalized, LanguageTag::PrivateUse::DISALLOW) );
        CPPUNIT_ASSERT_EQUAL( u"en-US"_ustr, aCanonicalized );
        CPPUNIT_ASSERT( !LanguageTag::isValidBcp47( u"x-foobar"_ustr, &aCanonicalized, LanguageTag::PrivateUse::DISALLOW) );
        CPPUNIT_ASSERT_EQUAL( u"x-foobar"_ustr, aCanonicalized );
        CPPUNIT_ASSERT( LanguageTag::isValidBcp47( u"qaa"_ustr, &aCanonicalized, LanguageTag::PrivateUse::DISALLOW) );
        CPPUNIT_ASSERT_EQUAL( u"qaa"_ustr, aCanonicalized );
        CPPUNIT_ASSERT( LanguageTag::isValidBcp47( u"de-Latn-DE"_ustr, &aCanonicalized) );
        CPPUNIT_ASSERT_EQUAL( u"de-DE"_ustr, aCanonicalized );
        /* TODO: at least some (those we know) grandfathered tags should be
         * recognized by the replacement code. */
        CPPUNIT_ASSERT( LanguageTag::isValidBcp47( u"en-GB-oed"_ustr, &aCanonicalized) );
        // en-GB-oed has become deprecated in updated language-subtag-registry database
        // (liblangtag 0.5.7)
        CPPUNIT_ASSERT( ( aCanonicalized == "en-GB-oxendict" ) || ( aCanonicalized == "en-GB-oed" ) );
    }
}

bool checkMapping( std::u16string_view rStr1, std::u16string_view rStr2 )
{
    if (rStr1 == u"la"          ) return rStr2 == u"la-VA";
    if (rStr1 == u"la-Latn"     ) return rStr2 == u"la-VA";
    if (rStr1 == u"tzm-Latn-DZ" ) return rStr2 == u"kab-DZ";
    if (rStr1 == u"bs-Latn-BA"  ) return rStr2 == u"bs-BA";
    if (rStr1 == u"bs-Latn"     ) return rStr2 == u"bs";
    if (rStr1 == u"cz"          ) return rStr2 == u"cs-CZ";
    if (rStr1 == u"iw-IL"       ) return rStr2 == u"he-IL";
    if (rStr1 == u"in-ID"       ) return rStr2 == u"id-ID";
    if (rStr1 == u"sr-YU"       ) return rStr2 == u"sr-CS";
    if (rStr1 == u"sh-RS"       ) return rStr2 == u"sr-Latn-RS";
    if (rStr1 == u"sh-YU"       ) return rStr2 == u"sr-Latn-CS";
    if (rStr1 == u"sh-CS"       ) return rStr2 == u"sr-Latn-CS";
    if (rStr1 == u"sh-ME"       ) return rStr2 == u"sr-Latn-ME";
    if (rStr1 == u"sh-BA"       ) return rStr2 == u"sr-Latn-BA";
    if (rStr1 == u"sh"          ) return rStr2 == u"sr-Latn";
    if (rStr1 == u"lah-PK"      ) return rStr2 == u"pnb-Arab-PK";
    if (rStr1 == u"pa-PK"       ) return rStr2 == u"pnb-Arab-PK";
    if (rStr1 == u"ca-XV"       ) return rStr2 == u"ca-ES-valencia";
    if (rStr1 == u"qcv-ES"      ) return rStr2 == u"ca-ES-valencia";
    if (rStr1 == u"ns-ZA"       ) return rStr2 == u"nso-ZA";
    if (rStr1 == u"ven-ZA"      ) return rStr2 == u"ve-ZA";
    if (rStr1 == u"qu-EC"       ) return rStr2 == u"quz-EC";
    if (rStr1 == u"qu-PE"       ) return rStr2 == u"quz-PE";
    if (rStr1 == u"ff-Latn-NG"  ) return rStr2 == u"ff-NG";
    if (rStr1 == u"fuv-NG"      ) return rStr2 == u"ff-NG";
    if (rStr1 == u"ji-IL"       ) return rStr2 == u"yi-IL";
    if (rStr1 == u"iu-CA"       ) return rStr2 == u"iu-Latn-CA";
    if (rStr1 == u"iu"          ) return rStr2 == u"iu-Latn";
    if (rStr1 == u"gbz-AF"      ) return rStr2 == u"prs-AF";
    if (rStr1 == u"ber-DZ"      ) return rStr2 == u"kab-DZ";
    if (rStr1 == u"tmz-MA"      ) return rStr2 == u"tzm-Tfng-MA";
    if (rStr1 == u"ber-MA"      ) return rStr2 == u"tzm-Tfng-MA";
    if (rStr1 == u"mg-MG"       ) return rStr2 == u"plt-MG";
    if (rStr1 == u"pli"         ) return rStr2 == u"pi-Latn";
    if (rStr1 == u"ks"          ) return rStr2 == u"ks-Arab";
    if (rStr1 == u"ks-IN"       ) return rStr2 == u"ks-Deva-IN";
    if (rStr1 == u"chr-US"      ) return rStr2 == u"chr-Cher-US";
    if (rStr1 == u"sd-PK"       ) return rStr2 == u"sd-Arab-PK";
    if (rStr1 == u"sr-Cyrl-RS"  ) return rStr2 == u"sr-RS";
    if (rStr1 == u"sr-Cyrl-ME"  ) return rStr2 == u"sr-ME";
    if (rStr1 == u"sr-Cyrl-BA"  ) return rStr2 == u"sr-BA";
    if (rStr1 == u"sr-Cyrl-CS"  ) return rStr2 == u"sr-CS";
    if (rStr1 == u"sr-Cyrl"     ) return rStr2 == u"sr";
    if (rStr1 == u"yi-Hebr-US"  ) return rStr2 == u"yi-US";
    if (rStr1 == u"yi-Hebr-IL"  ) return rStr2 == u"yi-IL";
    if (rStr1 == u"ha-NG"       ) return rStr2 == u"ha-Latn-NG";
    if (rStr1 == u"ha-GH"       ) return rStr2 == u"ha-Latn-GH";
    if (rStr1 == u"ku-Arab-IQ"  ) return rStr2 == u"ckb-IQ";
    if (rStr1 == u"ku-Arab"     ) return rStr2 == u"ckb";
    if (rStr1 == u"kmr-TR"      ) return rStr2 == u"kmr-Latn-TR";
    if (rStr1 == u"ku-TR"       ) return rStr2 == u"kmr-Latn-TR";
    if (rStr1 == u"kmr-SY"      ) return rStr2 == u"kmr-Latn-SY";
    if (rStr1 == u"ku-SY"       ) return rStr2 == u"kmr-Latn-SY";
    if (rStr1 == u"ku-IQ"       ) return rStr2 == u"ckb-IQ";
    if (rStr1 == u"ku-IR"       ) return rStr2 == u"ckb-IR";
    if (rStr1 == u"eu"          ) return rStr2 == u"eu-ES";
    if (rStr1 == u"crk-Latn-CN" ) return rStr2 == u"crk-Latn-CA";
    if (rStr1 == u"crk-Cans-CN" ) return rStr2 == u"crk-Cans-CA";
    if (rStr1 == u"en-GB-oed"   ) return rStr2 == u"en-GB-oxendict";
    if (rStr1 == u"es-ES_tradnl") return rStr2 == u"es-ES-u-co-trad";
    if (rStr1 == u"sd-IN"       ) return rStr2 == u"sd-Deva-IN";
    if (rStr1 == u"cmn-CN"      ) return rStr2 == u"zh-CN";
    if (rStr1 == u"cmn-TW"      ) return rStr2 == u"zh-TW";
    if (rStr1 == u"kw-UK"       ) return rStr2 == u"kw-GB";
    if (rStr1 == u"oc-FR"       ) return rStr2 == u"oc-FR-lengadoc";
    if (rStr1 == u"oc-ES"       ) return rStr2 == u"oc-ES-aranes";
    if (rStr1 == u"zh-Hans-CN"  ) return rStr2 == u"zh-CN";
    if (rStr1 == u"zh-Hant-TW"  ) return rStr2 == u"zh-TW";
    if (rStr1 == u"zh-Hans-SG"  ) return rStr2 == u"zh-SG";
    if (rStr1 == u"zh-Hant-HK"  ) return rStr2 == u"zh-HK";
    if (rStr1 == u"zh-Hant-MO"  ) return rStr2 == u"zh-MO";
    return rStr1 == rStr2;
}

void TestLanguageTag::testAllIsoLangEntries()
{
    const ::std::vector< MsLangId::LanguagetagMapping > aList( MsLangId::getDefinedLanguagetags());
    for (auto const& elem : aList)
    {
        bool b=false;
        if (elem.maBcp47 == "la-VA")
            b=true;
        (void)b;

        LanguageTag aTagString( elem.maBcp47, true);
        LanguageTag aTagID( elem.mnLang);
        if (!checkMapping( elem.maBcp47, aTagString.getBcp47()))
        {
            CPPUNIT_ASSERT_EQUAL_MESSAGE( "defined elem to tag", elem.maBcp47, aTagString.getBcp47() );
        }
        if (elem.maBcp47 != aTagID.getBcp47())
        {
            // There are multiple mappings, ID must be equal after conversions.
            LanguageTag aTagBack( aTagID.getBcp47(), true);
            if (aTagString.getLanguageType() != aTagBack.getLanguageType())
            {
                OString aMessage( OUStringToOString( elem.maBcp47, RTL_TEXTENCODING_ASCII_US));
                aMessage += " " + OUStringToOString( aTagString.getBcp47(), RTL_TEXTENCODING_ASCII_US) + ": " +
                    OUStringToOString( aTagString.getBcp47(), RTL_TEXTENCODING_ASCII_US) + " " +
                    OString::number( static_cast<sal_uInt16>(aTagString.getLanguageType()), 16) +
                    " -> " + OUStringToOString( aTagBack.getBcp47(), RTL_TEXTENCODING_ASCII_US) + " " +
                    OString::number( static_cast<sal_uInt16>(aTagBack.getLanguageType()), 16);
                CPPUNIT_ASSERT_EQUAL_MESSAGE( aMessage.getStr(), aTagBack.getLanguageType(), aTagString.getLanguageType());
            }
        }
#if 0
        // This does not hold, there are cases like 'ar'
        // LANGUAGE_ARABIC_PRIMARY_ONLY that when mapped back results in
        // 'ar-SA' as default locale.
        if (elem.mnLang != aTagString.getLanguageType())
        {
            // There are multiple mappings, string must be equal after conversions.
            LanguageTag aTagBack( aTagString.getLanguageType());
            if (aTagID.getBcp47() != aTagBack.getBcp47())
            {
                OString aMessage( OUStringToOString( elem.maBcp47, RTL_TEXTENCODING_ASCII_US));
                aMessage += " " + OUStringToOString( aTagID.getBcp47(), RTL_TEXTENCODING_ASCII_US) +
                    " -> " + OUStringToOString( aTagBack.getBcp47(), RTL_TEXTENCODING_ASCII_US);
                CPPUNIT_ASSERT_MESSAGE( aMessage.getStr(), aTagID.getBcp47() == aTagBack.getBcp47());
            }
        }
#endif
    }

    // Uncommenting this makes the test break and output SAL_WARN/INFO
    //CPPUNIT_ASSERT( true == false );
}

void TestLanguageTag::testDisplayNames()
{
    OUString aStr;
    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"en-US"_ustr), LanguageTag(u"en-US"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"English (United States)"_ustr, aStr);
    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"en-US"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"English (United States)"_ustr, aStr);
    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"en"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"English"_ustr, aStr);

    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"en-US"_ustr), LanguageTag(u"de-DE"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"Englisch (Vereinigte Staaten)"_ustr, aStr);
    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"en-US"_ustr), LanguageTag(u"de"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"Englisch (Vereinigte Staaten)"_ustr, aStr);
    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"en"_ustr), LanguageTag(u"de"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"Englisch"_ustr, aStr);

    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"de-DE"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"German (Germany)"_ustr, aStr);
    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"de"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"German"_ustr, aStr);
    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"de-DE-1901"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"German (Germany, Traditional German orthography)"_ustr, aStr);
    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"de-DE-1901"_ustr), LanguageTag(u"de-DE"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"Deutsch (Deutschland, Alte deutsche Rechtschreibung)"_ustr, aStr);

    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"en-GB"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"English (United Kingdom)"_ustr, aStr);
    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"en-GB-oxendict"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"English (United Kingdom, Oxford English Dictionary spelling)"_ustr, aStr);

    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"ca-ES-valencia"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"Catalan (Spain, Valencian)"_ustr, aStr);

    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"sr-Cyrl-RS"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"Serbian (Serbia)"_ustr, aStr);
    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"sr-Latn-RS"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"Serbian (Latin, Serbia)"_ustr, aStr);

    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"tlh"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"Klingon"_ustr, aStr);

    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"und"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"Unknown language"_ustr, aStr);

    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"zxx"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"No linguistic content"_ustr, aStr);

    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"x-lala"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"Unknown language (Private-Use=lala)"_ustr, aStr);
    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"x-lala"_ustr), LanguageTag(u"de"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"Unbekannte Sprache (Privatnutzung=lala)"_ustr, aStr);
    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"en-GB"_ustr), LanguageTag(u"x-lala"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"en (GB)"_ustr, aStr);
    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"en-GB-oxendict"_ustr), LanguageTag(u"x-lala"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"en (GB, OXENDICT)"_ustr, aStr);

    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"unreg-and-bad"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"unreg (Andorra, BAD)"_ustr, aStr);  // a tad of a surprise..

    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"en-029"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"English (Caribbean)"_ustr, aStr);
    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"fr-015"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"French (Northern Africa)"_ustr, aStr);

    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"qtz"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"qtz"_ustr, aStr);

    aStr = LanguageTagIcu::getDisplayName( LanguageTag(u"*"_ustr), LanguageTag(u"en"_ustr));
    CPPUNIT_ASSERT_EQUAL( u"*"_ustr, aStr);
}

void TestLanguageTag::testLanguagesWithoutHyphenation()
{
    // Arabic
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_PRIMARY_ONLY));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_ALGERIA));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_BAHRAIN));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_EGYPT));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_IRAQ));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_JORDAN));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_KUWAIT));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_LEBANON));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_LIBYA));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_MOROCCO));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_OMAN));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_QATAR));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_SAUDI_ARABIA));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_SYRIA));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_TUNISIA));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_UAE));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_ARABIC_YEMEN));

    // Pashto
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_PASHTO));

    // Persian/Farsi
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_FARSI));

    // Kashmiri
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_KASHMIRI));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_KASHMIRI_INDIA));

    // Central Kurdish (Sorani), uses Arabic script, does not have hyphenation
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_KURDISH_ARABIC_IRAQ));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_KURDISH_ARABIC_LSO));

    // Northern Kurdish (Kurmanji) -> uses Latin script, has hyphenation
    CPPUNIT_ASSERT(MsLangId::usesHyphenation(LANGUAGE_USER_KURDISH_SYRIA));
    CPPUNIT_ASSERT(MsLangId::usesHyphenation(LANGUAGE_USER_KURDISH_TURKEY));

    // Southern Kurdish -> uses Arabic script, does not have hyphenation
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_USER_KURDISH_SOUTHERN_IRAN));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_USER_KURDISH_SOUTHERN_IRAQ));

    // Punjabi
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_PUNJABI));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_PUNJABI_ARABIC_LSO));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_PUNJABI_PAKISTAN));

    // Sindhi
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_SINDHI));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_SINDHI_ARABIC_LSO));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_SINDHI_PAKISTAN));

    // Malai
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_MALAY_BRUNEI_DARUSSALAM));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_MALAY_MALAYSIA));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_USER_MALAY_ARABIC_BRUNEI));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_USER_MALAY_ARABIC_MALAYSIA));

    // Somali
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_SOMALI));

    // Swahili
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_SWAHILI));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_USER_SWAHILI_TANZANIA));

    // Urdu
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_URDU_INDIA));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_URDU_PAKISTAN));

    // CJK
    // Chinese
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_CHINESE));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_CHINESE_HONGKONG));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_CHINESE_LSO));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_CHINESE_MACAU));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_CHINESE_SIMPLIFIED));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_CHINESE_SIMPLIFIED_LEGACY));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_CHINESE_SINGAPORE));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_CHINESE_TRADITIONAL));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_CHINESE_TRADITIONAL_LSO));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_YUE_CHINESE_HONGKONG));
    // Japanese
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_JAPANESE));
    // Korean
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_KOREAN));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_KOREAN_JOHAB));
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_USER_KOREAN_NORTH));

    // Vietnamese
    CPPUNIT_ASSERT(!MsLangId::usesHyphenation(LANGUAGE_VIETNAMESE));
}

void TestLanguageTag::testLongBcp47ToStackString()
{
    // A qlt locale whose variant exceeds the StackString64 buffer must
    // not overflow. convertToBcp47 rejects it, leaving the buffer empty.
    lang::Locale aLocale;
    aLocale.Language = I18NLANGTAG_QLT;
    aLocale.Country = u"US"_ustr;
    aLocale.Variant = u"en-US-x-aaaa-bbbb-cccc-dddd-eeee-ffff-gggg-hhhh-"
                      "iiii-jjjj-kkkk-llll-mmmm-nnnn-oooo-pppp-qqqq-rrrr"_ustr;
    StackString64 aBuf;
    LanguageTag::convertToBcp47(aBuf, aLocale, false);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aBuf.getLength());
}

OUString makeRepeated(sal_Unicode c, sal_Int32 n)
{
    OUStringBuffer aBuf(n);
    for (sal_Int32 i = 0; i < n; ++i)
        aBuf.append(c);
    return aBuf.makeStringAndClear();
}

void TestLanguageTag::testLongLanguageToStackString()
{
    // A non-qlt locale with an overlong Language field and no Country
    // must not overflow the StackString64 buffer.
    lang::Locale aLocale;
    aLocale.Language = makeRepeated(u'a', 80);
    StackString64 aBuf;
    LanguageTag::convertToBcp47(aBuf, aLocale, false);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aBuf.getLength());
}

void TestLanguageTag::testLongLanguageCountryToStackString()
{
    // A non-qlt locale whose Language + "-" + Country exceeds the
    // StackString64 buffer must not overflow.
    lang::Locale aLocale;
    aLocale.Language = makeRepeated(u'a', 40);
    aLocale.Country = makeRepeated(u'B', 40);
    StackString64 aBuf;
    LanguageTag::convertToBcp47(aBuf, aLocale, false);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aBuf.getLength());
}

CPPUNIT_TEST_SUITE_REGISTRATION( TestLanguageTag );

}

CPPUNIT_PLUGIN_IMPLEMENT();

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
