new uno command uno:Translate with deepl api

New Uno command added for translation
right now it is only using deepl translation api

There's a section in the options > language settings
for setting up the api url and auth key

uno:Translate is a menu button under Format tab
which will bring up Language Selection dialog for translation.

DeepL can accept html as the input for translation, this new
feature leverages that by exporting paragraphs/selections to
html and paste them back without losing the formatting (in theory)
This works good in general but we may lose formatting in very complex
styled sentences.

Translation works in two ways;
1) Whole document
when there is no selection, it assumes that we want to translate whole
document. Each paragraphs is sent one by one so that the output timeout
can be minimum for each paragraph.
2) Selection

Change-Id: Ia2d3ab2f6757faf565b939e1d670a7dedac33390
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/140624
Tested-by: Jenkins
Reviewed-by: Miklos Vajna <[email protected]>
diff --git a/cui/Library_cui.mk b/cui/Library_cui.mk
index cb26653..5918be6 100644
--- a/cui/Library_cui.mk
+++ b/cui/Library_cui.mk
@@ -188,6 +188,7 @@
    cui/source/options/optgenrl \
    cui/source/options/opthtml \
    cui/source/options/optlanguagetool \
    cui/source/options/optdeepl \
    cui/source/options/optinet2 \
    cui/source/options/optjava \
    cui/source/options/optjsearch \
diff --git a/cui/UIConfig_cui.mk b/cui/UIConfig_cui.mk
index cdedcc15..89c8869 100644
--- a/cui/UIConfig_cui.mk
+++ b/cui/UIConfig_cui.mk
@@ -144,6 +144,7 @@
	cui/uiconfig/ui/optgeneralpage \
	cui/uiconfig/ui/opthtmlpage \
	cui/uiconfig/ui/langtoolconfigpage \
	cui/uiconfig/ui/deepltabpage \
	cui/uiconfig/ui/optionsdialog \
	cui/uiconfig/ui/optjsearchpage \
	cui/uiconfig/ui/optlanguagespage \
diff --git a/cui/inc/treeopt.hrc b/cui/inc/treeopt.hrc
index 8a56a6b..cdd7b58 100644
--- a/cui/inc/treeopt.hrc
+++ b/cui/inc/treeopt.hrc
@@ -56,7 +56,8 @@
    { NC_("SID_LANGUAGE_OPTIONS_RES", "Searching in Japanese"),  RID_SVXPAGE_JSEARCH_OPTIONS },
    { NC_("SID_LANGUAGE_OPTIONS_RES", "Asian Layout"),  RID_SVXPAGE_ASIAN_LAYOUT },
    { NC_("SID_LANGUAGE_OPTIONS_RES", "Complex Text Layout"),  RID_SVXPAGE_OPTIONS_CTL },
    { NC_("SID_LANGUAGE_OPTIONS_RES", "LanguageTool Server"),  RID_SVXPAGE_LANGTOOL_OPTIONS }
    { NC_("SID_LANGUAGE_OPTIONS_RES", "LanguageTool Server"),  RID_SVXPAGE_LANGTOOL_OPTIONS },
    { NC_("SID_LANGUAGE_OPTIONS_RES", "DeepL Server"),  RID_SVXPAGE_DEEPL_OPTIONS }
};

const std::pair<TranslateId, sal_uInt16> SID_INET_DLG_RES[] =
diff --git a/cui/source/options/optdeepl.cxx b/cui/source/options/optdeepl.cxx
new file mode 100644
index 0000000..b219845f
--- /dev/null
+++ b/cui/source/options/optdeepl.cxx
@@ -0,0 +1,53 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice 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/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "optdeepl.hxx"
#include <svtools/deeplcfg.hxx>

OptDeeplTabPage::OptDeeplTabPage(weld::Container* pPage, weld::DialogController* pController,
                                 const SfxItemSet& rSet)
    : SfxTabPage(pPage, pController, "cui/ui/deepltabpage.ui", "OptDeeplPage", &rSet)
    , m_xAPIUrl(m_xBuilder->weld_entry("apiurl"))
    , m_xAuthKey(m_xBuilder->weld_entry("authkey"))
{
}

OptDeeplTabPage::~OptDeeplTabPage() {}

void OptDeeplTabPage::Reset(const SfxItemSet*)
{
    SvxDeeplOptions& rDeeplOptions = SvxDeeplOptions::Get();
    m_xAPIUrl->set_text(rDeeplOptions.getAPIUrl());
    m_xAuthKey->set_text(rDeeplOptions.getAuthKey());
}

bool OptDeeplTabPage::FillItemSet(SfxItemSet*)
{
    SvxDeeplOptions& rDeeplOptions = SvxDeeplOptions::Get();
    rDeeplOptions.setAPIUrl(m_xAPIUrl->get_text());
    rDeeplOptions.setAuthKey(m_xAuthKey->get_text());
    return false;
}

std::unique_ptr<SfxTabPage> OptDeeplTabPage::Create(weld::Container* pPage,
                                                    weld::DialogController* pController,
                                                    const SfxItemSet* rAttrSet)
{
    return std::make_unique<OptDeeplTabPage>(pPage, pController, *rAttrSet);
}
diff --git a/cui/source/options/optdeepl.hxx b/cui/source/options/optdeepl.hxx
new file mode 100644
index 0000000..3258f67
--- /dev/null
+++ b/cui/source/options/optdeepl.hxx
@@ -0,0 +1,37 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice 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/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
#pragma once
#include <sfx2/tabdlg.hxx>

class OptDeeplTabPage : public SfxTabPage
{
public:
    OptDeeplTabPage(weld::Container* pPage, weld::DialogController* pController,
                    const SfxItemSet& rSet);
    virtual ~OptDeeplTabPage() override;
    static std::unique_ptr<SfxTabPage>
    Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rAttrSet);

    virtual bool FillItemSet(SfxItemSet* rSet) override;
    virtual void Reset(const SfxItemSet* rSet) override;

private:
    std::unique_ptr<weld::Entry> m_xAPIUrl;
    std::unique_ptr<weld::Entry> m_xAuthKey;
};
diff --git a/cui/source/options/treeopt.cxx b/cui/source/options/treeopt.cxx
index 1089114..805ddfc 100644
--- a/cui/source/options/treeopt.cxx
+++ b/cui/source/options/treeopt.cxx
@@ -63,6 +63,7 @@
#include <treeopt.hxx>
#include "optbasic.hxx"
#include "optlanguagetool.hxx"
#include "optdeepl.hxx"

#include <com/sun/star/awt/XContainerWindowEventHandler.hpp>
#include <com/sun/star/awt/ContainerWindowProvider.hpp>
@@ -298,6 +299,7 @@
        case RID_SVXPAGE_ACCESSIBILITYCONFIG:       fnCreate = &SvxAccessibilityOptionsTabPage::Create; break;
        case RID_SVXPAGE_OPTIONS_CTL:               fnCreate = &SvxCTLOptionsPage::Create ; break;
        case RID_SVXPAGE_LANGTOOL_OPTIONS:          fnCreate = &OptLanguageToolTabPage::Create ; break;
        case RID_SVXPAGE_DEEPL_OPTIONS:             fnCreate = &OptDeeplTabPage::Create ; break;
        case RID_SVXPAGE_OPTIONS_JAVA:              fnCreate = &SvxJavaOptionsPage::Create ; break;
#if HAVE_FEATURE_OPENCL
        case RID_SVXPAGE_OPENCL:                    fnCreate = &SvxOpenCLTabPage::Create ; break;
diff --git a/cui/uiconfig/ui/deepltabpage.ui b/cui/uiconfig/ui/deepltabpage.ui
new file mode 100644
index 0000000..1694bc5
--- /dev/null
+++ b/cui/uiconfig/ui/deepltabpage.ui
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2 -->
<interface domain="cui">
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkBox" id="OptDeeplPage">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="orientation">vertical</property>
    <child>
      <object class="GtkGrid" id="grid1">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="column_homogeneous">True</property>
        <child>
          <object class="GtkLabel" id="label1">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="halign">start</property>
            <property name="xpad">5</property>
            <property name="ypad">5</property>
            <property name="label" translatable="yes" context="deepltabpage|label1">DeepL API Options</property>
            <property name="use_underline">True</property>
            <property name="mnemonic_widget">grid1</property>
            <attributes>
              <attribute name="weight" value="bold"/>
            </attributes>
          </object>
          <packing>
            <property name="left_attach">0</property>
            <property name="top_attach">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkLinkButton" id="privacy">
            <property name="label" translatable="yes" context="deepltabpage|privacy">Please read the privacy policy</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="halign">start</property>
            <property name="relief">none</property>
            <property name="uri">https://www.deepl.com/privacy/</property>
          </object>
          <packing>
            <property name="left_attach">0</property>
            <property name="top_attach">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkGrid" id="grid2">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="margin_start">5</property>
            <property name="margin_end">5</property>
            <property name="margin_top">5</property>
            <property name="margin_bottom">5</property>
            <property name="row_spacing">6</property>
            <property name="column_spacing">5</property>
            <child>
              <object class="GtkLabel" id="label2">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="halign">start</property>
                <property name="margin_start">5</property>
                <property name="label" translatable="yes" context="deepltabpage|privacy">API URL:</property>
                <property name="use_underline">True</property>
                <property name="mnemonic_widget">apiurl</property>
              </object>
              <packing>
                <property name="left_attach">0</property>
                <property name="top_attach">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkLabel" id="label3">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="halign">start</property>
                <property name="margin_end">5</property>
                <property name="label" translatable="yes" context="deepltabpage|label3">Auth Key:</property>
                <property name="use_underline">True</property>
                <property name="mnemonic_widget">authkey</property>
              </object>
              <packing>
                <property name="left_attach">0</property>
                <property name="top_attach">1</property>
              </packing>
            </child>
            <child>
              <object class="GtkEntry" id="apiurl">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="hexpand">True</property>
              </object>
              <packing>
                <property name="left_attach">1</property>
                <property name="top_attach">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkEntry" id="authkey">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="hexpand">True</property>
              </object>
              <packing>
                <property name="left_attach">1</property>
                <property name="top_attach">1</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="left_attach">0</property>
            <property name="top_attach">2</property>
          </packing>
        </child>
      </object>
      <packing>
        <property name="expand">False</property>
        <property name="fill">True</property>
        <property name="position">0</property>
      </packing>
    </child>
  </object>
</interface>
diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx
index 2be264d..388ca17 100644
--- a/desktop/source/lib/init.cxx
+++ b/desktop/source/lib/init.cxx
@@ -128,6 +128,7 @@
#include <tools/json_writer.hxx>
#include <svtools/ctrltool.hxx>
#include <svtools/langtab.hxx>
#include <svtools/deeplcfg.hxx>
#include <vcl/fontcharmap.hxx>
#ifdef IOS
#include <vcl/sysdata.hxx>
@@ -6778,6 +6779,28 @@
    }
}

void setDeeplConfig()
{
    const char* pAPIUrlString = ::getenv("DEEPL_API_URL");
    const char* pAuthKeyString = ::getenv("DEEPL_AUTH_KEY");
    if (pAPIUrlString && pAuthKeyString)
    {
        OUString aAPIUrl = OStringToOUString(pAPIUrlString, RTL_TEXTENCODING_UTF8);
        OUString aAuthKey = OStringToOUString(pAuthKeyString, RTL_TEXTENCODING_UTF8);
        try
        {
            SvxDeeplOptions& rDeeplOptions = SvxDeeplOptions::Get();
            rDeeplOptions.setAPIUrl(aAPIUrl);
            rDeeplOptions.setAuthKey(aAuthKey);
        }
        catch(uno::Exception const& rException)
        {
            SAL_WARN("lok", "Failed to set Deepl API settings: " << rException.Message);
        }
    }
}


}

static int lo_initialize(LibreOfficeKit* pThis, const char* pAppPath, const char* pUserProfileUrl)
@@ -7092,6 +7115,7 @@
#endif

    setCertificateDir();
    setDeeplConfig();

    if (bNotebookbar)
    {
diff --git a/include/linguistic/translate.hxx b/include/linguistic/translate.hxx
new file mode 100644
index 0000000..985930c
--- /dev/null
+++ b/include/linguistic/translate.hxx
@@ -0,0 +1,9 @@
#pragma once
#include <linguistic/lngdllapi.h>
#include <rtl/string.hxx>

namespace linguistic
{
LNG_DLLPUBLIC OString Translate(const OString& rTargetLang, const OString& rAPIUrl,
                                const OString& rAuthKey, const OString& rData);
} // namespace
diff --git a/include/sfx2/pageids.hxx b/include/sfx2/pageids.hxx
index 9371848..1464ec4 100644
--- a/include/sfx2/pageids.hxx
+++ b/include/sfx2/pageids.hxx
@@ -57,6 +57,7 @@
#define RID_SVXPAGE_COLORCONFIG             (RID_SVX_START + 249)
#define RID_SVXPAGE_BASICIDE_OPTIONS        (RID_SVX_START + 209)
#define RID_SVXPAGE_LANGTOOL_OPTIONS        (RID_SVX_START + 210)
#define RID_SVXPAGE_DEEPL_OPTIONS           (RID_SVX_START + 211)

// Resource-Id's ------------------------------------------------------------

diff --git a/include/sfx2/sfxsids.hrc b/include/sfx2/sfxsids.hrc
index e37c789..8333ce7 100644
--- a/include/sfx2/sfxsids.hrc
+++ b/include/sfx2/sfxsids.hrc
@@ -224,6 +224,7 @@
#define SID_VIEW_DATA_SOURCE_BROWSER        (SID_SFX_START + 1660)
#define SID_UNPACK                          (SID_SFX_START + 1662)
// (SID_SFX_START + 1663) used further down
#define SID_ATTR_TARGETLANG_STR             (SID_SFX_START + 1664)
    // FREE
#define SID_OUTPUTSTREAM                    TypedWhichId<SfxUnoAnyItem>(SID_SFX_START + 1666)
#define SID_IMAGE_ORIENTATION               (SID_SFX_START + 1667)
diff --git a/include/svtools/deeplcfg.hxx b/include/svtools/deeplcfg.hxx
new file mode 100644
index 0000000..a943d72d
--- /dev/null
+++ b/include/svtools/deeplcfg.hxx
@@ -0,0 +1,50 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice 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/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#pragma once
#include <unotools/configitem.hxx>
#include <com/sun/star/uno/Sequence.hxx>
#include <svtools/svtdllapi.h>

using namespace utl;
using namespace com::sun::star::uno;

struct DeeplOptions_Impl;

class SVT_DLLPUBLIC SvxDeeplOptions final : public utl::ConfigItem
{
public:
    SvxDeeplOptions();
    virtual ~SvxDeeplOptions() override;

    virtual void Notify(const css::uno::Sequence<OUString>& _rPropertyNames) override;
    static SvxDeeplOptions& Get();

    const OUString& getAPIUrl() const;
    void setAPIUrl(const OUString& rVal);

    const OUString& getAuthKey() const;
    void setAuthKey(const OUString& rVal);

private:
    std::unique_ptr<DeeplOptions_Impl> pImpl;
    void Load(const css::uno::Sequence<OUString>& rPropertyNames);
    virtual void ImplCommit() override;
    static const Sequence<OUString>& GetPropertyNames();
};
diff --git a/include/svx/svxids.hrc b/include/svx/svxids.hrc
index 96122a6..20a2a29 100644
--- a/include/svx/svxids.hrc
+++ b/include/svx/svxids.hrc
@@ -587,7 +587,7 @@
#define SID_FM_FILECONTROL                              ( SID_SVX_START + 605 )
//( SID_SVX_START + 606 ) is used by SID_DRAWTBX_REDACTED_EXPORT
#define SID_FM_NAVIGATIONBAR                            ( SID_SVX_START + 607 )
//FREE
#define SID_FM_TRANSLATE                                ( SID_SVX_START + 608 )
//FREE
#define SID_FM_DELETEROWS                               ( SID_SVX_START + 610 )
//FREE
diff --git a/include/vcl/htmltransferable.hxx b/include/vcl/htmltransferable.hxx
new file mode 100644
index 0000000..2576c7e3
--- /dev/null
+++ b/include/vcl/htmltransferable.hxx
@@ -0,0 +1,51 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice 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/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#pragma once

#include <com/sun/star/datatransfer/XTransferable.hpp>
#include <cppuhelper/weak.hxx>
#include <rtl/ustring.hxx>
#include <vcl/dllapi.h>

namespace vcl::unohelper
{
// Helper class for passing HTML string as XTransferable to TransferableDataHelper object
class VCL_DLLPUBLIC HtmlTransferable final : public css::datatransfer::XTransferable,
                                             public ::cppu::OWeakObject
{
private:
    OString data;

public:
    HtmlTransferable(OString sData);
    virtual ~HtmlTransferable() override;

    // css::uno::XInterface
    css::uno::Any SAL_CALL queryInterface(const css::uno::Type& rType) override;
    void SAL_CALL acquire() noexcept override { OWeakObject::acquire(); }
    void SAL_CALL release() noexcept override { OWeakObject::release(); }

    // css::datatransfer::XTransferable
    css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& aFlavor) override;
    css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL getTransferDataFlavors() override;
    sal_Bool SAL_CALL isDataFlavorSupported(const css::datatransfer::DataFlavor& aFlavor) override;
};

} // namespace vcl::unohelper
diff --git a/linguistic/Library_lng.mk b/linguistic/Library_lng.mk
index cff45e3..6ac44d7 100644
--- a/linguistic/Library_lng.mk
+++ b/linguistic/Library_lng.mk
@@ -51,6 +51,7 @@
	boost_headers \
	icuuc \
	icu_headers \
	curl \
))

$(eval $(call gb_Library_add_exception_objects,lng,\
@@ -72,6 +73,7 @@
	linguistic/source/spelldsp \
	linguistic/source/spelldta \
	linguistic/source/thesdsp \
	linguistic/source/translate \
))

# vim: set noet sw=4 ts=4:
diff --git a/linguistic/source/translate.cxx b/linguistic/source/translate.cxx
new file mode 100644
index 0000000..5337c33
--- /dev/null
+++ b/linguistic/source/translate.cxx
@@ -0,0 +1,71 @@
#include <linguistic/translate.hxx>
#include <sal/log.hxx>
#include <curl/curl.h>
#include <sal/log.hxx>
#include <rtl/string.h>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <vcl/htmltransferable.hxx>
#include <tools/long.hxx>

namespace linguistic
{
OString Translate(const OString& rTargetLang, const OString& rAPIUrl, const OString& rAuthKey,
                  const OString& rData)
{
    constexpr tools::Long CURL_TIMEOUT = 10L;

    std::unique_ptr<CURL, std::function<void(CURL*)>> curl(curl_easy_init(),
                                                           [](CURL* p) { curl_easy_cleanup(p); });
    curl_easy_setopt(curl.get(), CURLOPT_URL, rAPIUrl.getStr());
    curl_easy_setopt(curl.get(), CURLOPT_FAILONERROR, 1L);
    curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT, CURL_TIMEOUT);

    std::string response_body;
    curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION,
                     +[](void* buffer, size_t size, size_t nmemb, void* userp) -> size_t {
                         if (!userp)
                             return 0;
                         std::string* response = static_cast<std::string*>(userp);
                         size_t real_size = size * nmemb;
                         response->append(static_cast<char*>(buffer), real_size);
                         return real_size;
                     });
    curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, static_cast<void*>(&response_body));
    OString aLang(curl_easy_escape(curl.get(), rTargetLang.getStr(), rTargetLang.getLength()));
    OString aAuthKey(curl_easy_escape(curl.get(), rAuthKey.getStr(), rAuthKey.getLength()));
    OString aData(curl_easy_escape(curl.get(), rData.getStr(), rData.getLength()));
    OString aPostData("auth_key=" + aAuthKey + "&target_lang=" + aLang + "&text=" + aData);

    curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, aPostData.getStr());
    CURLcode cc = curl_easy_perform(curl.get());
    if (cc != CURLE_OK)
    {
        SAL_WARN("linguistic",
                 "Translate: CURL perform returned with error: " << static_cast<sal_Int32>(cc));
        return {};
    }
    tools::Long nStatusCode;
    curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &nStatusCode);
    if (nStatusCode != 200)
    {
        SAL_WARN("linguistic",
                 "Translate: CURL request returned with status code: " << nStatusCode);
        return {};
    }
    // parse the response
    boost::property_tree::ptree root;
    std::stringstream aStream(response_body.data());
    boost::property_tree::read_json(aStream, root);
    boost::property_tree::ptree& translations = root.get_child("translations");
    size_t size = translations.size();
    if (size <= 0)
    {
        SAL_WARN("linguistic", "Translate: API did not return any translations");
    }
    // take the first one
    const boost::property_tree::ptree& translation = translations.begin()->second;
    const std::string text = translation.get<std::string>("text");
    return OString(text);
}
}
diff --git a/officecfg/registry/data/org/openoffice/Office/UI/WriterCommands.xcu b/officecfg/registry/data/org/openoffice/Office/UI/WriterCommands.xcu
index 9513c20..1c20f95 100644
--- a/officecfg/registry/data/org/openoffice/Office/UI/WriterCommands.xcu
+++ b/officecfg/registry/data/org/openoffice/Office/UI/WriterCommands.xcu
@@ -1349,6 +1349,14 @@
          <value>1</value>
        </prop>
      </node>
      <node oor:name=".uno:Translate" oor:op="replace">
        <prop oor:name="Label" oor:type="xs:string">
          <value xml:lang="en-US">Translate...</value>
        </prop>
        <prop oor:name="Properties" oor:type="xs:int">
          <value>1</value>
        </prop>
      </node>
      <node oor:name=".uno:FormatColumns" oor:op="replace">
        <prop oor:name="Label" oor:type="xs:string">
          <value xml:lang="en-US">Co~lumns...</value>
diff --git a/officecfg/registry/schema/org/openoffice/Office/Linguistic.xcs b/officecfg/registry/schema/org/openoffice/Office/Linguistic.xcs
index fe9365d..2ecc4ae 100644
--- a/officecfg/registry/schema/org/openoffice/Office/Linguistic.xcs
+++ b/officecfg/registry/schema/org/openoffice/Office/Linguistic.xcs
@@ -408,6 +408,28 @@
          </prop>
      </group>
    </group>
    <group oor:name="Translation">
      <info>
        <desc>Contains translation relevant settings.</desc>
      </info>
      <group oor:name="Deepl">
        <info>
          <desc>Contains DeepL API relevant settings.</desc>
        </info>
        <prop oor:name="ApiURL" oor:type="xs:string">
          <info>
            <desc>Deepl Translator API URL</desc>
            <label>URL for the Deepl translator api</label>
          </info>
        </prop>
        <prop oor:name="AuthKey" oor:type="xs:string">
          <info>
            <desc>Deepl Translator API URL Authkey</desc>
            <label>Auth key for the Deepl translator api</label>
          </info>
        </prop>
      </group>
    </group>
    <group oor:name="Hyphenation">
      <info>
        <desc>Contains hyphenation relevant settings.</desc>
diff --git a/svtools/Library_svt.mk b/svtools/Library_svt.mk
index bc7d07b..a2bc1aa 100644
--- a/svtools/Library_svt.mk
+++ b/svtools/Library_svt.mk
@@ -84,6 +84,7 @@
    svtools/source/config/fontsubstconfig \
    svtools/source/config/htmlcfg \
    svtools/source/config/languagetoolcfg \
    svtools/source/config/deeplcfg \
    svtools/source/config/itemholder2 \
    svtools/source/config/miscopt \
    svtools/source/config/slidesorterbaropt \
diff --git a/svtools/source/config/deeplcfg.cxx b/svtools/source/config/deeplcfg.cxx
new file mode 100644
index 0000000..3b022b7
--- /dev/null
+++ b/svtools/source/config/deeplcfg.cxx
@@ -0,0 +1,124 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice 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/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <sal/log.hxx>
#include <sal/config.h>
#include <svtools/deeplcfg.hxx>
#include <com/sun/star/uno/Sequence.hxx>
#include <tools/debug.hxx>

using namespace utl;
using namespace com::sun::star::uno;

struct DeeplOptions_Impl
{
    OUString sAPIUrl;
    OUString sAuthKey;
};

const Sequence<OUString>& SvxDeeplOptions::GetPropertyNames()
{
    static Sequence<OUString> const aNames{
        "Deepl/ApiURL",
        "Deepl/AuthKey",
    };
    return aNames;
}

const OUString& SvxDeeplOptions::getAPIUrl() const { return pImpl->sAPIUrl; }

void SvxDeeplOptions::setAPIUrl(const OUString& rVal)
{
    pImpl->sAPIUrl = rVal;
    SetModified();
}

const OUString& SvxDeeplOptions::getAuthKey() const { return pImpl->sAuthKey; }

void SvxDeeplOptions::setAuthKey(const OUString& rVal)
{
    pImpl->sAuthKey = rVal;
    SetModified();
}

namespace
{
class theSvxDeeplOptions : public rtl::Static<SvxDeeplOptions, theSvxDeeplOptions>
{
};
}

SvxDeeplOptions& SvxDeeplOptions::Get() { return theSvxDeeplOptions::get(); }

SvxDeeplOptions::SvxDeeplOptions()
    : ConfigItem("Office.Linguistic/Translation")
    , pImpl(new DeeplOptions_Impl)
{
    Load(GetPropertyNames());
}

SvxDeeplOptions::~SvxDeeplOptions() {}
void SvxDeeplOptions::Notify(const css::uno::Sequence<OUString>&) { Load(GetPropertyNames()); }

void SvxDeeplOptions::Load(const css::uno::Sequence<OUString>& aNames)
{
    Sequence<Any> aValues = GetProperties(aNames);
    const Any* pValues = aValues.getConstArray();
    DBG_ASSERT(aValues.getLength() == aNames.getLength(), "GetProperties failed");
    if (aValues.getLength() != aNames.getLength())
        return;
    for (int nProp = 0; nProp < aNames.getLength(); nProp++)
    {
        if (!pValues[nProp].hasValue())
            continue;
        switch (nProp)
        {
            case 0:
                pValues[nProp] >>= pImpl->sAPIUrl;
                break;
            case 1:
                pValues[nProp] >>= pImpl->sAuthKey;
                break;
            default:
                break;
        }
    }
}

void SvxDeeplOptions::ImplCommit()
{
    const Sequence<OUString>& aNames = GetPropertyNames();
    Sequence<Any> aValues(aNames.getLength());
    Any* pValues = aValues.getArray();
    for (int nProp = 0; nProp < aNames.getLength(); nProp++)
    {
        switch (nProp)
        {
            case 0:
                pValues[nProp] <<= pImpl->sAPIUrl;
                break;
            case 1:
                pValues[nProp] <<= pImpl->sAuthKey;
                break;
            default:
                break;
        }
    }
    PutProperties(aNames, aValues);
}
diff --git a/svx/sdi/svx.sdi b/svx/sdi/svx.sdi
index 0336b6c..c53fefd 100644
--- a/svx/sdi/svx.sdi
+++ b/svx/sdi/svx.sdi
@@ -1596,6 +1596,23 @@
    GroupId = SfxGroupId::Controls;
]

SfxBoolItem Translate SID_FM_TRANSLATE
(SfxStringItem TargetLang SID_ATTR_TARGETLANG_STR)
[
    AutoUpdate = FALSE,
    FastCall = TRUE,
    ReadOnlyDoc = FALSE,
    Toggle = FALSE,
    Container = FALSE,
    RecordAbsolute = FALSE,
    RecordPerSet;

    AccelConfig = FALSE,
    MenuConfig = FALSE,
    ToolBoxConfig = FALSE,
    GroupId = SfxGroupId::Format;
]


SfxBoolItem Combobox SID_INSERT_COMBOBOX

diff --git a/sw/Library_sw.mk b/sw/Library_sw.mk
index 2269486..d5c59fa 100644
--- a/sw/Library_sw.mk
+++ b/sw/Library_sw.mk
@@ -694,6 +694,7 @@
    sw/source/uibase/shells/grfsh \
    sw/source/uibase/shells/grfshex \
    sw/source/uibase/shells/langhelper \
    sw/source/uibase/shells/translatehelper \
    sw/source/uibase/shells/listsh \
    sw/source/uibase/shells/mediash \
    sw/source/uibase/shells/navsh \
diff --git a/sw/Library_swui.mk b/sw/Library_swui.mk
index 9187d3c..8e9222d 100644
--- a/sw/Library_swui.mk
+++ b/sw/Library_swui.mk
@@ -79,6 +79,7 @@
    vcl \
    drawinglayercore \
    drawinglayer \
    lng \
))

$(eval $(call gb_Library_add_exception_objects,swui,\
@@ -152,6 +153,7 @@
    sw/source/ui/misc/pgfnote \
    sw/source/ui/misc/pggrid \
    sw/source/ui/misc/srtdlg \
    sw/source/ui/misc/translatelangselect \
    sw/source/ui/misc/swmodalredlineacceptdlg \
    sw/source/ui/misc/titlepage \
    sw/source/ui/table/colwd \
diff --git a/sw/UIConfig_swriter.mk b/sw/UIConfig_swriter.mk
index f4e003e..02ea3de 100644
--- a/sw/UIConfig_swriter.mk
+++ b/sw/UIConfig_swriter.mk
@@ -188,6 +188,7 @@
	sw/uiconfig/swriter/ui/insertautotextdialog \
	sw/uiconfig/swriter/ui/insertbookmark \
	sw/uiconfig/swriter/ui/insertbreak \
	sw/uiconfig/swriter/ui/translationdialog \
	sw/uiconfig/swriter/ui/insertcaption \
	sw/uiconfig/swriter/ui/insertdbcolumnsdialog \
	sw/uiconfig/swriter/ui/insertfootnote \
diff --git a/sw/inc/strings.hrc b/sw/inc/strings.hrc
index 81a5428..4f2dd39 100644
--- a/sw/inc/strings.hrc
+++ b/sw/inc/strings.hrc
@@ -299,6 +299,7 @@
#define STR_DELETE_NOTE_AUTHOR                  NC_("STR_DELETE_NOTE_AUTHOR", "Delete ~All Comments by $1")
#define STR_HIDE_NOTE_AUTHOR                    NC_("STR_HIDE_NOTE_AUTHOR", "H~ide All Comments by $1")
#define STR_OUTLINE_NUMBERING                   NC_("STR_OUTLINE_NUMBERING", "Chapter Numbering")
#define STR_STATSTR_SWTRANSLATE                 NC_("STR_STATSTR_SWTRANSLATE", "Translating document...")
/* To translators: $1 == will be replaced by STR_WORDCOUNT_WORDARG, and $2 by STR_WORDCOUNT_COLARG
   e.g. Selected: 1 word, 2 characters */
#define STR_WORDCOUNT                           NC_("STR_WORDCOUNT", "Selected: $1, $2")
diff --git a/sw/inc/swabstdlg.hxx b/sw/inc/swabstdlg.hxx
index 5c5f2bc..02b38a7 100644
--- a/sw/inc/swabstdlg.hxx
+++ b/sw/inc/swabstdlg.hxx
@@ -383,6 +383,18 @@
    virtual sal_uInt16          GetRestartPage() const = 0;
};

class SwLanguageListItem;

class AbstractSwTranslateLangSelectDlg
{
protected:
    virtual ~AbstractSwTranslateLangSelectDlg() = default;
public:
    virtual std::shared_ptr<weld::DialogController> getDialogController() = 0;
    virtual std::optional<SwLanguageListItem> GetSelectedLanguage() = 0;
};


class SwAbstractDialogFactory
{
public:
@@ -406,6 +418,7 @@
    CreateSwContentControlListItemDlg(weld::Window* pParent, SwContentControlListItem& rItem) = 0;

    virtual std::shared_ptr<AbstractSwBreakDlg> CreateSwBreakDlg(weld::Window *pParent, SwWrtShell &rSh) = 0;
    virtual std::shared_ptr<AbstractSwTranslateLangSelectDlg> CreateSwTranslateLangSelectDlg(weld::Window *pParent, SwWrtShell &rSh) = 0;
    virtual VclPtr<VclAbstractDialog> CreateSwChangeDBDlg(SwView& rVw) = 0;
    virtual VclPtr<SfxAbstractTabDialog>  CreateSwCharDlg(weld::Window* pParent, SwView& pVw, const SfxItemSet& rCoreSet,
        SwCharDlgMode nDialogMode, const OUString* pFormatStr = nullptr) = 0;
diff --git a/sw/sdi/_textsh.sdi b/sw/sdi/_textsh.sdi
index 05f6af0..6830a24 100644
--- a/sw/sdi/_textsh.sdi
+++ b/sw/sdi/_textsh.sdi
@@ -1814,5 +1814,11 @@
        StateMethod = GetState ;
    ]

    SID_FM_TRANSLATE
    [
        ExecMethod = Execute ;
        StateMethod = GetState ;
    ]

}  // end of interface text

diff --git a/sw/source/ui/dialog/swdlgfact.cxx b/sw/source/ui/dialog/swdlgfact.cxx
index 1aa438f..6e9ebc8 100644
--- a/sw/source/ui/dialog/swdlgfact.cxx
+++ b/sw/source/ui/dialog/swdlgfact.cxx
@@ -89,6 +89,7 @@
#include <uiborder.hxx>
#include <mmresultdialogs.hxx>
#include <formatlinebreak.hxx>
#include <translatelangselect.hxx>

using namespace ::com::sun::star;
using namespace css::frame;
@@ -796,6 +797,12 @@
    return m_xDlg->GetRestartPage();
}

std::optional<SwLanguageListItem> AbstractSwTranslateLangSelectDlg_Impl::GetSelectedLanguage()
{
    SwTranslateLangSelectDlg* pDlg = dynamic_cast<SwTranslateLangSelectDlg*>(m_xDlg.get());
    return pDlg->GetSelectedLanguage();
}

VclPtr<AbstractSwInsertAbstractDlg> SwAbstractDialogFactory_Impl::CreateSwInsertAbstractDlg(weld::Window* pParent)
{
    return VclPtr<AbstractSwInsertAbstractDlg_Impl>::Create(std::make_unique<SwInsertAbstractDlg>(pParent));
@@ -855,6 +862,11 @@
    return std::make_shared<AbstractSwBreakDlg_Impl>(std::make_unique<SwBreakDlg>(pParent, rSh));
}

std::shared_ptr<AbstractSwTranslateLangSelectDlg> SwAbstractDialogFactory_Impl::CreateSwTranslateLangSelectDlg(weld::Window* pParent, SwWrtShell &rSh)
{
    return std::make_shared<AbstractSwTranslateLangSelectDlg_Impl>(std::make_unique<SwTranslateLangSelectDlg>(pParent, rSh));
}

VclPtr<VclAbstractDialog> SwAbstractDialogFactory_Impl::CreateSwChangeDBDlg(SwView& rVw)
{
#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS
diff --git a/sw/source/ui/dialog/swdlgfact.hxx b/sw/source/ui/dialog/swdlgfact.hxx
index c127566..f97ff43 100644
--- a/sw/source/ui/dialog/swdlgfact.hxx
+++ b/sw/source/ui/dialog/swdlgfact.hxx
@@ -188,6 +188,19 @@
    virtual std::shared_ptr<weld::DialogController> getDialogController() override { return m_xDlg; }
};

class AbstractSwTranslateLangSelectDlg_Impl : public AbstractSwTranslateLangSelectDlg
{
    std::shared_ptr<weld::DialogController> m_xDlg;
public:
    explicit AbstractSwTranslateLangSelectDlg_Impl(std::shared_ptr<weld::DialogController> p)
        : m_xDlg(std::move(p))
    {
    }

    virtual std::shared_ptr<weld::DialogController> getDialogController() override { return m_xDlg; }
    virtual std::optional<SwLanguageListItem> GetSelectedLanguage() override;
};

class AbstractSwTableWidthDlg_Impl : public VclAbstractDialog
{
    std::unique_ptr<SwTableWidthDlg> m_xDlg;
@@ -684,6 +697,7 @@
                                      SwContentControlListItem& rItem) override;

    virtual std::shared_ptr<AbstractSwBreakDlg> CreateSwBreakDlg(weld::Window *pParent, SwWrtShell &rSh) override;
    virtual std::shared_ptr<AbstractSwTranslateLangSelectDlg> CreateSwTranslateLangSelectDlg(weld::Window *pParent, SwWrtShell &rSh) override;
    virtual VclPtr<VclAbstractDialog> CreateSwChangeDBDlg(SwView& rVw) override;
    virtual VclPtr<SfxAbstractTabDialog>  CreateSwCharDlg(weld::Window* pParent, SwView& pVw, const SfxItemSet& rCoreSet,
        SwCharDlgMode nDialogMode, const OUString* pFormatStr = nullptr) override;
diff --git a/sw/source/ui/misc/translatelangselect.cxx b/sw/source/ui/misc/translatelangselect.cxx
new file mode 100644
index 0000000..7e6d02c
--- /dev/null
+++ b/sw/source/ui/misc/translatelangselect.cxx
@@ -0,0 +1,153 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice 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/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <vcl/svapp.hxx>
#include <osl/diagnose.h>
#include <uitool.hxx>
#include <swtypes.hxx>
#include <wrtsh.hxx>
#include <view.hxx>
#include <viewopt.hxx>
#include <translatelangselect.hxx>
#include <pagedesc.hxx>
#include <poolfmt.hxx>
#include <sal/log.hxx>
#include <ndtxt.hxx>
#include <shellio.hxx>
#include <svtools/deeplcfg.hxx>
#include <vcl/idle.hxx>
#include <mdiexp.hxx>
#include <strings.hrc>
#include <com/sun/star/task/XStatusIndicator.hpp>
#include <sfx2/viewfrm.hxx>
#include <com/sun/star/task/XStatusIndicatorFactory.hpp>
#include <linguistic/translate.hxx>

int SwTranslateLangSelectDlg::selectedLangIdx = -1;
SwTranslateLangSelectDlg::SwTranslateLangSelectDlg(weld::Window* pParent, SwWrtShell& rSh)
    : GenericDialogController(pParent, "modules/swriter/ui/translationdialog.ui",
                              "LanguageSelectDialog")
    , rWrtSh(rSh)
    , m_xLanguageListBox(m_xBuilder->weld_combo_box("combobox1"))
    , m_xBtnCancel(m_xBuilder->weld_button("cancel"))
    , m_xBtnTranslate(m_xBuilder->weld_button("translate"))
    , m_xLanguageVec({
          SwLanguageListItem("BG", "Bulgarian"),
          SwLanguageListItem("CS", "Czech"),
          SwLanguageListItem("DA", "Danish"),
          SwLanguageListItem("DE", "German"),
          SwLanguageListItem("EL", "Greek"),
          SwLanguageListItem("EN-GB", "English (British)"),
          SwLanguageListItem("EN-US", "English (American)"),
          SwLanguageListItem("ET", "Estonian"),
          SwLanguageListItem("FI", "Finnish"),
          SwLanguageListItem("FR", "French"),
          SwLanguageListItem("HU", "Hungarian"),
          SwLanguageListItem("ID", "Indonesian"),
          SwLanguageListItem("IT", "Italian"),
          SwLanguageListItem("JA", "Japanese"),
          SwLanguageListItem("LT", "Lithuanian"),
          SwLanguageListItem("LV", "Dutch"),
          SwLanguageListItem("PL", "Polish"),
          SwLanguageListItem("PT-BR", "Portuguese (Brazilian)"),
          SwLanguageListItem("PT-PT", "Portuguese (European)"),
          SwLanguageListItem("RO", "Romanian"),
          SwLanguageListItem("RU", "Russian"),
          SwLanguageListItem("SK", "Slovak"),
          SwLanguageListItem("SL", "Slovenian"),
          SwLanguageListItem("SV", "Swedish"),
          SwLanguageListItem("TR", "Turkish"),
          SwLanguageListItem("ZH", "Chinese (simplified)"),
      })
    , m_bTranslationStarted(false)
    , m_bCancelTranslation(false)
{
    m_xLanguageListBox->connect_changed(LINK(this, SwTranslateLangSelectDlg, LangSelectHdl));
    m_xBtnCancel->connect_clicked(LINK(this, SwTranslateLangSelectDlg, LangSelectCancelHdl));
    m_xBtnTranslate->connect_clicked(LINK(this, SwTranslateLangSelectDlg, LangSelectTranslateHdl));

    for (const auto& item : m_xLanguageVec)
    {
        m_xLanguageListBox->append_text(OStringToOUString(item.getName(), RTL_TEXTENCODING_UTF8));
    }

    if (SwTranslateLangSelectDlg::selectedLangIdx != -1)
    {
        m_xLanguageListBox->set_active(SwTranslateLangSelectDlg::selectedLangIdx);
    }
}

std::optional<SwLanguageListItem> SwTranslateLangSelectDlg::GetSelectedLanguage()
{
    if (SwTranslateLangSelectDlg::selectedLangIdx != -1)
    {
        return m_xLanguageVec.at(SwTranslateLangSelectDlg::selectedLangIdx);
    }

    return {};
}

IMPL_STATIC_LINK(SwTranslateLangSelectDlg, LangSelectHdl, weld::ComboBox&, rBox, void)
{
    const auto selected = rBox.get_active();
    SwTranslateLangSelectDlg::selectedLangIdx = selected;
}

IMPL_LINK_NOARG(SwTranslateLangSelectDlg, LangSelectCancelHdl, weld::Button&, void)
{
    // stop translation first
    if (m_bTranslationStarted)
        m_bCancelTranslation = true;
    else
        m_xDialog->response(RET_CANCEL);
}

IMPL_LINK_NOARG(SwTranslateLangSelectDlg, LangSelectTranslateHdl, weld::Button&, void)
{
    if (SwTranslateLangSelectDlg::selectedLangIdx == -1)
    {
        m_xDialog->response(RET_CANCEL);
        return;
    }

    SvxDeeplOptions& rDeeplOptions = SvxDeeplOptions::Get();
    if (rDeeplOptions.getAPIUrl().isEmpty() || rDeeplOptions.getAuthKey().isEmpty())
    {
        SAL_WARN("sw.ui", "SwTranslateLangSelectDlg: API options are not set");
        m_xDialog->response(RET_CANCEL);
        return;
    }

    const OString aAPIUrl
        = OUStringToOString(rtl::Concat2View(rDeeplOptions.getAPIUrl() + "?tag_handling=html"),
                            RTL_TEXTENCODING_UTF8)
              .trim();
    const OString aAuthKey
        = OUStringToOString(rDeeplOptions.getAuthKey(), RTL_TEXTENCODING_UTF8).trim();
    const auto aTargetLang
        = m_xLanguageVec.at(SwTranslateLangSelectDlg::selectedLangIdx).getLanguage();

    m_bTranslationStarted = true;

    SwTranslateHelper::TranslateAPIConfig aConfig({ aAPIUrl, aAuthKey, aTargetLang });
    SwTranslateHelper::TranslateDocumentCancellable(rWrtSh, aConfig, m_bCancelTranslation);
    m_xDialog->response(RET_OK);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/uibase/inc/translatehelper.hxx b/sw/source/uibase/inc/translatehelper.hxx
new file mode 100644
index 0000000..906b527
--- /dev/null
+++ b/sw/source/uibase/inc/translatehelper.hxx
@@ -0,0 +1,42 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice 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/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
#pragma once
#include <swtypes.hxx>

class SwWrtShell;
class SwPaM;
class SwNode;
class SwTextNode;

namespace SwTranslateHelper
{
struct SW_DLLPUBLIC TranslateAPIConfig final
{
    const OString m_xAPIUrl;
    const OString m_xAuthKey;
    const OString m_xTargetLanguage;
};
SW_DLLPUBLIC OString ExportPaMToHTML(SwPaM* pCursor, bool bReplacePTag);
SW_DLLPUBLIC void PasteHTMLToPaM(SwWrtShell& rWrtSh, SwPaM* pCursor, const OString& rData,
                                 bool bSetSelection);
SW_DLLPUBLIC void TranslateDocument(SwWrtShell& rWrtSh, const TranslateAPIConfig& rConfig);
SW_DLLPUBLIC void TranslateDocumentCancellable(SwWrtShell& rWrtSh,
                                               const TranslateAPIConfig& rConfig,
                                               bool& rCancelTranslation);
}
diff --git a/sw/source/uibase/inc/translatelangselect.hxx b/sw/source/uibase/inc/translatelangselect.hxx
new file mode 100644
index 0000000..4190aaf
--- /dev/null
+++ b/sw/source/uibase/inc/translatelangselect.hxx
@@ -0,0 +1,69 @@

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice 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/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
#pragma once
#include <vcl/weld.hxx>
#include <rtl/string.h>
#include <vector>
#include <optional>
#include "translatehelper.hxx"

class SwWrtShell;

// SwLanguageListItem Helper class for displaying available languages with their names and tags on the listbox.
class SwLanguageListItem final
{
public:
    SwLanguageListItem(const OString& sLanguage, const OString& sName)
        : m_sLanguage(sLanguage)
        , m_sName(sName)
    {
    }
    const OString& getLanguage() const { return m_sLanguage; }
    const OString& getName() const { return m_sName; }

private:
    const OString m_sLanguage;
    const OString m_sName;
};

// SwTranslateLangSelectDlg Language selection dialog for translation API.
class SwTranslateLangSelectDlg final : public weld::GenericDialogController
{
public:
    static int selectedLangIdx;
    SwTranslateLangSelectDlg(weld::Window* pParent, SwWrtShell& rSh);
    std::optional<SwLanguageListItem> GetSelectedLanguage();

private:
    SwWrtShell& rWrtSh;
    std::unique_ptr<weld::ComboBox> m_xLanguageListBox;
    std::unique_ptr<weld::Button> m_xBtnCancel;
    std::unique_ptr<weld::Button> m_xBtnTranslate;
    std::vector<SwLanguageListItem> m_xLanguageVec;

    bool m_bTranslationStarted;
    bool m_bCancelTranslation;

    DECL_STATIC_LINK(SwTranslateLangSelectDlg, LangSelectHdl, weld::ComboBox&, void);
    DECL_LINK(LangSelectCancelHdl, weld::Button&, void);
    DECL_LINK(LangSelectTranslateHdl, weld::Button&, void);
};

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/uibase/shells/textsh1.cxx b/sw/source/uibase/shells/textsh1.cxx
index b7cfc8d..da69a34 100644
--- a/sw/source/uibase/shells/textsh1.cxx
+++ b/sw/source/uibase/shells/textsh1.cxx
@@ -103,6 +103,9 @@
#include <bookmark.hxx>
#include <linguistic/misc.hxx>
#include <authfld.hxx>
#include <translatelangselect.hxx>
#include <svtools/deeplcfg.hxx>
#include <translatehelper.hxx>

using namespace ::com::sun::star;
using namespace com::sun::star::beans;
@@ -1489,6 +1492,32 @@
        }
    }
    break;
    case SID_FM_TRANSLATE:
    {
        const SfxPoolItem* pTargetLangStringItem = nullptr;
        if (pArgs && SfxItemState::SET == pArgs->GetItemState(SID_ATTR_TARGETLANG_STR, false, &pTargetLangStringItem))
        {
            SvxDeeplOptions& rDeeplOptions = SvxDeeplOptions::Get();
            if (rDeeplOptions.getAPIUrl().isEmpty() || rDeeplOptions.getAuthKey().isEmpty())
            {
                SAL_WARN("sw.ui", "SID_FM_TRANSLATE: API options are not set");
                break;
            }
            const OString aAPIUrl = OUStringToOString(rtl::Concat2View(rDeeplOptions.getAPIUrl() + "?tag_handling=html"), RTL_TEXTENCODING_UTF8).trim();
            const OString aAuthKey = OUStringToOString(rDeeplOptions.getAuthKey(), RTL_TEXTENCODING_UTF8).trim();
            OString aTargetLang = OUStringToOString(static_cast<const SfxStringItem*>(pTargetLangStringItem)->GetValue(), RTL_TEXTENCODING_UTF8);
            SwTranslateHelper::TranslateAPIConfig aConfig({aAPIUrl, aAuthKey, aTargetLang});
            SwTranslateHelper::TranslateDocument(rWrtSh, aConfig);
        }
        else
        {
            SwAbstractDialogFactory* pFact = SwAbstractDialogFactory::Create();
            std::shared_ptr<AbstractSwTranslateLangSelectDlg> pAbstractDialog(pFact->CreateSwTranslateLangSelectDlg(GetView().GetFrameWeld(), rWrtSh));
            std::shared_ptr<weld::DialogController> pDialogController(pAbstractDialog->getDialogController());
            weld::DialogController::runAsync(pDialogController, [] (sal_Int32 /*nResult*/) { });
        }
    }
    break;
    case SID_SPELLCHECK_IGNORE:
    {
        SwPaM *pPaM = rWrtSh.GetCursor();
diff --git a/sw/source/uibase/shells/translatehelper.cxx b/sw/source/uibase/shells/translatehelper.cxx
new file mode 100644
index 0000000..92f0991
--- /dev/null
+++ b/sw/source/uibase/shells/translatehelper.cxx
@@ -0,0 +1,208 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice 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/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
#include <wrtsh.hxx>
#include <pam.hxx>
#include <node.hxx>
#include <ndtxt.hxx>
#include <translatehelper.hxx>
#include <sal/log.hxx>
#include <rtl/string.h>
#include <shellio.hxx>
#include <vcl/scheduler.hxx>
#include <vcl/svapp.hxx>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <vcl/htmltransferable.hxx>
#include <vcl/transfer.hxx>
#include <swdtflvr.hxx>
#include <linguistic/translate.hxx>
#include <com/sun/star/task/XStatusIndicator.hpp>
#include <sfx2/viewfrm.hxx>
#include <com/sun/star/task/XStatusIndicatorFactory.hpp>
#include <strings.hrc>

namespace SwTranslateHelper
{
OString ExportPaMToHTML(SwPaM* pCursor, bool bReplacePTag)
{
    SolarMutexGuard gMutex;
    OString aResult;
    WriterRef xWrt;
    GetHTMLWriter(u"NoLineLimit,SkipHeaderFooter", OUString(), xWrt);
    if (pCursor != nullptr)
    {
        SvMemoryStream aMemoryStream;
        SwWriter aWriter(aMemoryStream, *pCursor);
        ErrCode nError = aWriter.Write(xWrt);
        if (nError.IsError())
        {
            SAL_WARN("sw.ui", "ExportPaMToHTML: failed to export selection to HTML");
            return {};
        }
        aResult
            = OString(static_cast<const char*>(aMemoryStream.GetData()), aMemoryStream.GetSize());
        if (bReplacePTag)
        {
            aResult = aResult.replaceAll("<p", "<span");
            aResult = aResult.replaceAll("</p>", "</span>");
        }
        return aResult;
    }
    return {};
}

void PasteHTMLToPaM(SwWrtShell& rWrtSh, SwPaM* pCursor, const OString& rData, bool bSetSelection)
{
    SolarMutexGuard gMutex;
    rtl::Reference<vcl::unohelper::HtmlTransferable> pHtmlTransferable
        = new vcl::unohelper::HtmlTransferable(rData);
    if (pHtmlTransferable.is())
    {
        TransferableDataHelper aDataHelper(pHtmlTransferable);
        if (aDataHelper.GetXTransferable().is()
            && SwTransferable::IsPasteSpecial(rWrtSh, aDataHelper))
        {
            if (bSetSelection)
            {
                rWrtSh.SetSelection(*pCursor);
            }
            SwTransferable::Paste(rWrtSh, aDataHelper);
            rWrtSh.KillSelection(nullptr, false);
        }
    }
}

void TranslateDocument(SwWrtShell& rWrtSh, const TranslateAPIConfig& rConfig)
{
    bool bCancel = false;
    TranslateDocumentCancellable(rWrtSh, rConfig, bCancel);
}

void TranslateDocumentCancellable(SwWrtShell& rWrtSh, const TranslateAPIConfig& rConfig,
                                  bool& rCancelTranslation)
{
    auto m_pCurrentPam = rWrtSh.GetCursor();
    bool bHasSelection = rWrtSh.HasSelection();

    if (bHasSelection)
    {
        // iteration will start top to bottom
        if (m_pCurrentPam->GetPoint()->nNode > m_pCurrentPam->GetMark()->nNode)
            m_pCurrentPam->Exchange();
    }

    auto const& pNodes = rWrtSh.GetNodes();
    SwPosition aPoint = *m_pCurrentPam->GetPoint();
    SwPosition aMark = *m_pCurrentPam->GetMark();
    auto startNode = bHasSelection ? aPoint.nNode.GetIndex() : SwNodeOffset(0);
    auto endNode = bHasSelection ? aMark.nNode.GetIndex() : pNodes.Count() - 1;

    sal_Int32 nCount(0);
    sal_Int32 nProgress(0);

    for (SwNodeOffset n(startNode); n <= endNode; ++n)
    {
        if (pNodes[n] && pNodes[n]->IsTextNode())
        {
            if (pNodes[n]->GetTextNode()->GetText().isEmpty())
                continue;
            nCount++;
        }
    }

    SfxViewFrame* pFrame = SfxViewFrame::Current();
    uno::Reference<frame::XFrame> xFrame = pFrame->GetFrame().GetFrameInterface();
    uno::Reference<task::XStatusIndicatorFactory> xProgressFactory(xFrame, uno::UNO_QUERY);
    uno::Reference<task::XStatusIndicator> xStatusIndicator;

    if (xProgressFactory.is())
    {
        xStatusIndicator = xProgressFactory->createStatusIndicator();
    }

    if (xStatusIndicator.is())
        xStatusIndicator->start(SwResId(STR_STATSTR_SWTRANSLATE), nCount);

    for (SwNodeOffset n(startNode); n <= endNode; ++n)
    {
        if (rCancelTranslation)
            break;

        if (n >= rWrtSh.GetNodes().Count())
            break;

        if (!pNodes[n])
            break;

        SwNode* pNode = pNodes[n];
        if (pNode->IsTextNode())
        {
            if (pNode->GetTextNode()->GetText().isEmpty())
                continue;

            auto cursor
                = Writer::NewUnoCursor(*rWrtSh.GetDoc(), pNode->GetIndex(), pNode->GetIndex());

            // set edges (start, end) for nodes inside the selection.
            if (bHasSelection)
            {
                if (startNode == endNode)
                {
                    cursor->SetMark();
                    cursor->GetPoint()->nContent = aPoint.nContent;
                    cursor->GetMark()->nContent = aMark.nContent;
                }
                else if (n == startNode)
                {
                    cursor->SetMark();
                    cursor->GetPoint()->nContent = std::min(aPoint.nContent, aMark.nContent);
                }
                else if (n == endNode)
                {
                    cursor->SetMark();
                    cursor->GetMark()->nContent = aMark.nContent;
                    cursor->GetPoint()->nContent = 0;
                }
            }

            const auto aOut = SwTranslateHelper::ExportPaMToHTML(cursor.get(), true);
            const auto aTranslatedOut = linguistic::Translate(
                rConfig.m_xTargetLanguage, rConfig.m_xAPIUrl, rConfig.m_xAuthKey, aOut);
            SwTranslateHelper::PasteHTMLToPaM(rWrtSh, cursor.get(), aTranslatedOut, true);

            if (xStatusIndicator.is())
                xStatusIndicator->setValue((100 * ++nProgress) / nCount);

            Idle aIdle("ProgressBar::SetValue aIdle");
            aIdle.SetPriority(TaskPriority::POST_PAINT);
            aIdle.Start();

            rWrtSh.LockView(true);
            while (aIdle.IsActive() && !Application::IsQuit())
            {
                Application::Yield();
            }
            rWrtSh.LockView(false);
        }
    }

    if (xStatusIndicator.is())
        xStatusIndicator->end();
}
}
diff --git a/sw/uiconfig/sglobal/menubar/menubar.xml b/sw/uiconfig/sglobal/menubar/menubar.xml
index 6246d443..4e305c1 100644
--- a/sw/uiconfig/sglobal/menubar/menubar.xml
+++ b/sw/uiconfig/sglobal/menubar/menubar.xml
@@ -720,6 +720,8 @@
      <menu:menuitem menu:id=".uno:WordCountDialog" menu:style="text"/>
      <menu:menuitem menu:id=".uno:AccessibilityCheck"/>
      <menu:menuseparator/>
      <menu:menuitem menu:id=".uno:Translate" menu:style="text"/>
      <menu:menuseparator/>
      <menu:menu menu:id=".uno:AutoFormatMenu">
        <menu:menupopup>
          <menu:menuitem menu:id=".uno:OnlineAutoFormat"/>
diff --git a/sw/uiconfig/swriter/menubar/menubar.xml b/sw/uiconfig/swriter/menubar/menubar.xml
index 45a7c3b..06823b5 100644
--- a/sw/uiconfig/swriter/menubar/menubar.xml
+++ b/sw/uiconfig/swriter/menubar/menubar.xml
@@ -745,6 +745,8 @@
      <menu:menuitem menu:id=".uno:WordCountDialog"/>
      <menu:menuitem menu:id=".uno:AccessibilityCheck"/>
      <menu:menuseparator/>
      <menu:menuitem menu:id=".uno:Translate"/>
      <menu:menuseparator/>
      <menu:menu menu:id=".uno:AutoFormatMenu">
        <menu:menupopup>
          <menu:menuitem menu:id=".uno:OnlineAutoFormat"/>
diff --git a/sw/uiconfig/swriter/ui/translationdialog.ui b/sw/uiconfig/swriter/ui/translationdialog.ui
new file mode 100644
index 0000000..a6aff92
--- /dev/null
+++ b/sw/uiconfig/swriter/ui/translationdialog.ui
@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2 -->
<interface domain="sw">
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkDialog" id="LanguageSelectDialog">
    <property name="can_focus">False</property>
    <property name="border_width">6</property>
    <property name="title" translatable="yes" context="LanguageSelectDialog">Language Selection</property>
    <property name="resizable">False</property>
    <property name="modal">True</property>
    <property name="default_width">0</property>
    <property name="default_height">0</property>
    <property name="type_hint">dialog</property>
    <child type="titlebar">
      <placeholder/>
    </child>
    <child internal-child="vbox">
      <object class="GtkBox" id="dialog-vbox1">
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <property name="spacing">2</property>
        <child internal-child="action_area">
          <object class="GtkButtonBox" id="action-area1">
            <property name="can_focus">False</property>
            <property name="layout_style">end</property>
            <child>
              <object class="GtkButton" id="cancel">
                <property name="label">_Cancel</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="use_underline">True</property>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkButton" id="translate">
                <property name="label">_OK</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="use_underline">True</property>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkGrid" id="grid1">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="row_spacing">5</property>
            <child>
              <object class="GtkLabel" id="label1">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="halign">start</property>
                <property name="label" translatable="yes" context="LanguageSelectDialog">Select the target language for translation</property>
                <property name="mnemonic_widget">combobox1</property>
              </object>
              <packing>
                <property name="left_attach">0</property>
                <property name="top_attach">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkComboBoxText" id="combobox1">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="hexpand">True</property>
              </object>
              <packing>
                <property name="left_attach">0</property>
                <property name="top_attach">1</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">0</property>
          </packing>
        </child>
      </object>
    </child>
    <action-widgets>
      <action-widget response="-6">cancel</action-widget>
      <action-widget response="-5">translate</action-widget>
    </action-widgets>
  </object>
</interface>
diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index a432ceb..4b17510 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -412,6 +412,7 @@
    vcl/source/app/svmain \
    vcl/source/app/timer \
    vcl/source/app/unohelp2 \
    vcl/source/app/htmltransferable \
    vcl/source/app/unohelp \
    vcl/source/app/vclevent \
    vcl/source/app/watchdog \
diff --git a/vcl/jsdialog/enabled.cxx b/vcl/jsdialog/enabled.cxx
index 6dbc32c..f260691 100644
--- a/vcl/jsdialog/enabled.cxx
+++ b/vcl/jsdialog/enabled.cxx
@@ -57,6 +57,7 @@
        || rUIFile == u"xmlsec/ui/digitalsignaturesdialog.ui"
        || rUIFile == u"xmlsec/ui/viewcertdialog.ui" || rUIFile == u"xmlsec/ui/certgeneral.ui"
        || rUIFile == u"xmlsec/ui/certpage.ui" || rUIFile == u"svx/ui/accessibilitycheckdialog.ui"
        || rUIFile == u"modules/swriter/ui/translationdialog.ui"
        || rUIFile == u"svx/ui/accessibilitycheckentry.ui")
    {
        return true;
diff --git a/vcl/source/app/htmltransferable.cxx b/vcl/source/app/htmltransferable.cxx
new file mode 100644
index 0000000..24f65fe
--- /dev/null
+++ b/vcl/source/app/htmltransferable.cxx
@@ -0,0 +1,78 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice 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/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <vcl/htmltransferable.hxx>
#include <sot/exchange.hxx>
#include <sot/formats.hxx>
#include <vcl/svapp.hxx>
#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
#include <cppuhelper/queryinterface.hxx>
#include <boost/property_tree/json_parser.hpp>

using namespace ::com::sun::star;

namespace vcl::unohelper
{
HtmlTransferable::HtmlTransferable(OString sData)
    : data(sData)
{
}

HtmlTransferable::~HtmlTransferable() {}

// css::uno::XInterface
uno::Any HtmlTransferable::queryInterface(const uno::Type& rType)
{
    uno::Any aRet = ::cppu::queryInterface(rType, static_cast<datatransfer::XTransferable*>(this));
    return (aRet.hasValue() ? aRet : OWeakObject::queryInterface(rType));
}

// css::datatransfer::XTransferable
uno::Any HtmlTransferable::getTransferData(const datatransfer::DataFlavor& rFlavor)
{
    SotClipboardFormatId nT = SotExchange::GetFormat(rFlavor);
    if (nT != SotClipboardFormatId::HTML)
    {
        throw datatransfer::UnsupportedFlavorException();
    }
    size_t size = data.getLength();
    uno::Sequence<sal_Int8> sData(size);
    std::memcpy(sData.getArray(), data.getStr(), size);
    return uno::Any(sData);
}

uno::Sequence<datatransfer::DataFlavor> HtmlTransferable::getTransferDataFlavors()
{
    uno::Sequence<datatransfer::DataFlavor> aDataFlavors(1);
    auto ref = aDataFlavors.getArray()[0];
    ref.MimeType = "text/html";
    ref.DataType = cppu::UnoType<uno::Sequence<sal_Int8>>::get();
    SotExchange::GetFormatDataFlavor(SotClipboardFormatId::HTML, aDataFlavors.getArray()[0]);
    return aDataFlavors;
}

sal_Bool HtmlTransferable::isDataFlavorSupported(const datatransfer::DataFlavor& rFlavor)
{
    SotClipboardFormatId nT = SotExchange::GetFormat(rFlavor);
    return (nT == SotClipboardFormatId::HTML);
}

} // namespace vcl::unohelper