tdf#152459: fallback to language-only when looking for grammar checker

LanguageTool registers itself for languages like "en-US", "es-AR", "de-DE";
but also for generic "en", "es", "de". When the requested language, like
"fr-CH" or "es-PH", is not found in the registered grammar checkers, the
fallback strings will be attempted.

Change-Id: Id460db8d378f246ea98191d22bdb3537fd1aee1c
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/148201
Tested-by: Jenkins
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit e7ae5b4793fb74309d5d1f32c3c696d07071d676)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/148232
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
diff --git a/linguistic/source/gciterator.cxx b/linguistic/source/gciterator.cxx
index 581f356..993b025 100644
--- a/linguistic/source/gciterator.cxx
+++ b/linguistic/source/gciterator.cxx
@@ -488,8 +488,30 @@ void GrammarCheckingIterator::ProcessResult(
}


std::pair<OUString, std::optional<OUString>>
GrammarCheckingIterator::getServiceForLocale(const lang::Locale& rLocale) const
{
    if (!rLocale.Language.isEmpty())
    {
        const OUString sBcp47 = LanguageTag::convertToBcp47(rLocale, false);
        GCImplNames_t::const_iterator aLangIt(m_aGCImplNamesByLang.find(sBcp47));
        if (aLangIt != m_aGCImplNamesByLang.end())
            return { aLangIt->second, {} };

        for (const auto& sFallbackBcp47 : LanguageTag(rLocale).getFallbackStrings(false))
        {
            aLangIt = m_aGCImplNamesByLang.find(sFallbackBcp47);
            if (aLangIt != m_aGCImplNamesByLang.end())
                return { aLangIt->second, sFallbackBcp47 };
        }
    }

    return {};
}


uno::Reference< linguistic2::XProofreader > GrammarCheckingIterator::GetGrammarChecker(
    const lang::Locale &rLocale )
    lang::Locale &rLocale )
{
    uno::Reference< linguistic2::XProofreader > xRes;

@@ -503,11 +525,11 @@ uno::Reference< linguistic2::XProofreader > GrammarCheckingIterator::GetGrammarC
        m_bGCServicesChecked = true;
    }

    const LanguageType nLang = LanguageTag::convertToLanguageType( rLocale, false);
    GCImplNames_t::const_iterator aLangIt( m_aGCImplNamesByLang.find( nLang ) );
    if (aLangIt != m_aGCImplNamesByLang.end())  // matching configured language found?
    if (const auto& [aSvcImplName, oFallbackBcp47] = getServiceForLocale(rLocale);
        !aSvcImplName.isEmpty()) // matching configured language found?
    {
        OUString aSvcImplName( aLangIt->second );
        if (oFallbackBcp47)
            rLocale = LanguageTag::convertToLocale(*oFallbackBcp47, false);
        GCReferences_t::const_iterator aImplNameIt( m_aGCReferencesByService.find( aSvcImplName ) );
        if (aImplNameIt != m_aGCReferencesByService.end())  // matching impl name found?
        {
@@ -1086,8 +1108,7 @@ void GrammarCheckingIterator::GetConfiguredGCSvcs_Impl()
                {
                    // only the first entry is used, there should be only one grammar checker per language
                    const OUString aImplName( aImplNames[0] );
                    const LanguageType nLang = LanguageTag::convertToLanguageType( rElementName );
                    aTmpGCImplNamesByLang[ nLang ] = aImplName;
                    aTmpGCImplNamesByLang[rElementName] = aImplName;
                }
            }
            else
@@ -1135,17 +1156,17 @@ void GrammarCheckingIterator::SetServiceList(
{
    ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );

    LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
    OUString sBcp47 = LanguageTag::convertToBcp47(rLocale, false);
    OUString aImplName;
    if (rSvcImplNames.hasElements())
        aImplName = rSvcImplNames[0];   // there is only one grammar checker per language

    if (!LinguIsUnspecified(nLanguage) && nLanguage != LANGUAGE_DONTKNOW)
    if (!LinguIsUnspecified(sBcp47) && !sBcp47.isEmpty())
    {
        if (!aImplName.isEmpty())
            m_aGCImplNamesByLang[ nLanguage ] = aImplName;
            m_aGCImplNamesByLang[sBcp47] = aImplName;
        else
            m_aGCImplNamesByLang.erase( nLanguage );
            m_aGCImplNamesByLang.erase(sBcp47);
    }
}

@@ -1155,11 +1176,7 @@ uno::Sequence< OUString > GrammarCheckingIterator::GetServiceList(
{
    ::osl::Guard< ::osl::Mutex > aGuard( MyMutex() );

    OUString aImplName;     // there is only one grammar checker per language
    LanguageType nLang  = LinguLocaleToLanguage( rLocale );
    GCImplNames_t::const_iterator aIt( m_aGCImplNamesByLang.find( nLang ) );
    if (aIt != m_aGCImplNamesByLang.end())
        aImplName = aIt->second;
    const OUString aImplName = getServiceForLocale(rLocale).first;     // there is only one grammar checker per language

    if (!aImplName.isEmpty())
        return { aImplName };
diff --git a/linguistic/source/gciterator.hxx b/linguistic/source/gciterator.hxx
index c4cc076..f159a9a 100644
--- a/linguistic/source/gciterator.hxx
+++ b/linguistic/source/gciterator.hxx
@@ -40,6 +40,8 @@
#include <i18nlangtag/lang.h>

#include <map>
#include <optional>
#include <utility>
#include <deque>

#include "defs.hxx"
@@ -97,8 +99,8 @@ class GrammarCheckingIterator:
    DocMap_t        m_aDocIdMap;


    // language -> implname mapping
    typedef std::map< LanguageType, OUString > GCImplNames_t;
    // BCP-47 language tag -> implname mapping
    typedef std::map< OUString, OUString > GCImplNames_t;
    GCImplNames_t   m_aGCImplNamesByLang;

    // implname -> UNO reference mapping
@@ -136,13 +138,19 @@ class GrammarCheckingIterator:
    sal_Int32 GetSuggestedEndOfSentence( const OUString &rText, sal_Int32 nSentenceStartPos, const css::lang::Locale &rLocale );

    void GetConfiguredGCSvcs_Impl();
    css::uno::Reference< css::linguistic2::XProofreader > GetGrammarChecker( const css::lang::Locale & rLocale );
    css::uno::Reference< css::linguistic2::XProofreader > GetGrammarChecker( css::lang::Locale & rLocale );

    css::uno::Reference< css::util::XChangesBatch > const & GetUpdateAccess() const;

    GrammarCheckingIterator( const GrammarCheckingIterator & ) = delete;
    GrammarCheckingIterator & operator = ( const GrammarCheckingIterator & ) = delete;

    // Gets the grammar checker service, using fallback locales if necessary,
    // and the BCP-47 tag for the updated locale, if the fallback was used.
    // Precondition: MyMutex() is locked.
    std::pair<OUString, std::optional<OUString>>
    getServiceForLocale(const css::lang::Locale& rLocale) const;

public:

    void DequeueAndCheck();