LibreOffice plugin to pipe whole Writer documents through Google Translate, that ought to keep most of the page formatting.

⌈⌋ ⎇ branch:  PageTranslate


Check-in [0790399062]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Rework config dialog to use dropdown/listbox for selecting service. Change config schema, and uno control handling.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 07903990627d20b349fe321052c495020121fc09
User & Date: mario 2021-05-13 03:55:35
Context
2021-05-13
03:56
Add settings link in Tools→PageTranslate menu (albeit to general OO settings dialog, not PT leaf). check-in: 8d54c76a7b user: mario tags: trunk
03:55
Rework config dialog to use dropdown/listbox for selecting service. Change config schema, and uno control handling. check-in: 0790399062 user: mario tags: trunk
2021-05-12
20:31
Prepare DeepL free API (albeit probably broken), and deep-translate pacakge, also allow more shell-like placeholders for CLI usage and fix for Python 2.7 support. check-in: d8bb91403f user: mario tags: trunk, 1.7
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to OptionsDialog.xdl.

1
2
3




4














5
6
7
8
9
10
11
12


13
14
15
16
17
18
19
20
21

22
23
24
25
26
27
28







29
30
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dlg:window PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "dialog.dtd">
<dlg:window xmlns:dlg="http://openoffice.org/2000/dialog" xmlns:script="http://openoffice.org/2000/script" dlg:id="OptionsPageTranslate" dlg:help-url="vnd.sun.star.help://help/vnd.include-once.pagetranslate/config.xhp?Language=en&amp;System=UNIX&amp;UseDB=no" dlg:left="110" dlg:top="50" dlg:width="283" dlg:height="258" dlg:closeable="true" dlg:moveable="true" dlg:title="Title" dlg:withtitlebar="false">




 <dlg:bulletinboard>














  <dlg:radiogroup>
   <dlg:radio dlg:id="google" dlg:tab-index="0" dlg:left="10" dlg:top="10" dlg:width="115" dlg:height="12" dlg:help-text="Standard translation service" dlg:value="Google Translate web service" dlg:group-name="backend" dlg:checked="true"/>
   <dlg:radio dlg:id="deepl_web" dlg:tab-index="1" dlg:left="10" dlg:top="25" dlg:width="115" dlg:height="12" dlg:help-text="broken again" dlg:value="DeepL web interface" dlg:group-name="backend"/>
   <dlg:radio dlg:id="deepl_api" dlg:tab-index="2" dlg:left="10" dlg:top="45" dlg:width="95" dlg:height="19" dlg:help-text="Requires subscription and API key" dlg:value="DeepL translation API" dlg:multiline="true" dlg:group-name="backend"/>
   <dlg:radio dlg:id="microsoft" dlg:tab-index="3" dlg:left="10" dlg:top="62" dlg:width="95" dlg:height="18" dlg:help-text="Requires an OAuth secret in API key" dlg:value="Microsoft Translator API" dlg:multiline="true" dlg:group-name="backend"/>
   <dlg:radio dlg:id="mymemory" dlg:tab-index="4" dlg:left="10" dlg:top="85" dlg:width="84" dlg:height="19" dlg:help-text="Add an email address instead of API key" dlg:value="MyMemory" dlg:multiline="true" dlg:group-name="backend"/>
   <dlg:radio dlg:id="cli" dlg:tab-index="5" dlg:left="10" dlg:top="105" dlg:width="83" dlg:height="18" dlg:help-text="For instace `translate-cli -t en {}`" dlg:value="command line tool" dlg:multiline="true" dlg:group-name="backend"/>
  </dlg:radiogroup>


  <dlg:textfield dlg:id="api_key" dlg:tab-index="6" dlg:left="115" dlg:top="53" dlg:width="93" dlg:height="12" dlg:help-text="Key required for DeepL, Microsoft Translator, or Google Translate API (not implemented here)"/>
  <dlg:titledbox dlg:id="FrameControl1" dlg:tab-index="7" dlg:disabled="true" dlg:left="110" dlg:top="39" dlg:width="105" dlg:height="36" dlg:help-text="requires an API key / else will use *SLOWER* web page requests">
   <dlg:title dlg:value="API key / OAuth secret"/>
  </dlg:titledbox>
  <dlg:checkbox dlg:id="frames" dlg:tab-index="8" dlg:left="10" dlg:top="149" dlg:width="184" dlg:height="12" dlg:help-text="Traverse subdocuments (frames / floating frames) as well" dlg:value="also iterate over TextFrames" dlg:checked="false"/>
  <dlg:checkbox dlg:id="quick" dlg:tab-index="9" dlg:left="10" dlg:top="132" dlg:width="185" dlg:height="12" dlg:help-text="Temporary placeholders instead of iterating over newline breaks. (Only tested with Google Translate. Might screw up others.)" dlg:value="quick paragraph linebreak handling" dlg:checked="false"/>
  <dlg:checkbox dlg:id="debug" dlg:tab-index="10" dlg:left="10" dlg:top="182" dlg:width="182" dlg:height="12" dlg:help-text="Log file in /tmp/pagetranslate-libreoffice.txt" dlg:value="additonal debugging" dlg:checked="true"/>
  <dlg:titledbox dlg:id="FrameControl2" dlg:tab-index="12" dlg:disabled="true" dlg:left="100" dlg:top="76" dlg:width="105" dlg:height="25" dlg:help-text="requires an API key / else will use *SLOWER* web page requests">
   <dlg:title dlg:value="Email address"/>

  </dlg:titledbox>
  <dlg:titledbox dlg:id="FrameControl3" dlg:tab-index="14" dlg:disabled="true" dlg:left="100" dlg:top="100" dlg:width="105" dlg:height="25" dlg:help-text="requires an API key / else will use *SLOWER* web page requests">
   <dlg:title dlg:value="CLI program"/>
  </dlg:titledbox>
  <dlg:textfield dlg:id="cmd" dlg:tab-index="13" dlg:left="106" dlg:top="109" dlg:width="93" dlg:height="12" dlg:help-text="Command to run, use `{}` as placeholder for text section"/>
  <dlg:textfield dlg:id="email" dlg:tab-index="11" dlg:left="105" dlg:top="85" dlg:width="93" dlg:height="12" dlg:help-text="MyMemory asks for an email addres (does not require it)"/>
  <dlg:checkbox dlg:id="slow" dlg:tab-index="15" dlg:left="11" dlg:top="165" dlg:width="183" dlg:height="12" dlg:help-text="Split sentences on formatting prior translation (= more roundtrips, less cohesive sentence structure / translation)" dlg:value="super slow mode (retain more inline-paragraph formatting)" dlg:checked="false"/>







 </dlg:bulletinboard>
</dlg:window>


|
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
<
<
<
|
|
|
>
>
|
<
|
|
<
<
<
<
|
>
|
<
<
|
<
<
<
>
>
>
>
>
>
>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24



25
26
27
28
29
30

31
32




33
34
35


36



37
38
39
40
41
42
43
44
45
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dlg:window PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "dialog.dtd">
<dlg:window xmlns:dlg="http://openoffice.org/2000/dialog" xmlns:script="http://openoffice.org/2000/script" dlg:id="OptionsPageTranslate" dlg:left="110" dlg:top="50" dlg:width="283" dlg:height="258" dlg:help-url="HIDID" dlg:closeable="true" dlg:moveable="true" dlg:title="Title" dlg:withtitlebar="false">
 <dlg:styles>
  <dlg:style dlg:style-id="0" dlg:font-height="8"/>
  <dlg:style dlg:style-id="1" dlg:font-height="8" dlg:font-underline="single"/>
 </dlg:styles>
 <dlg:bulletinboard>
  <dlg:textfield dlg:id="api_key" dlg:tab-index="4" dlg:left="172" dlg:top="30" dlg:width="75" dlg:height="10" dlg:help-text="Key required for DeepL, Microsoft Translator, or Google Translate API (not implemented here)" dlg:help-url="HIDID"/>
  <dlg:checkbox dlg:id="frames" dlg:tab-index="1" dlg:left="20" dlg:top="104" dlg:width="100" dlg:height="10" dlg:help-text="Traverse subdocuments (frames / floating frames) as well" dlg:help-url="HIDID" dlg:value="also iterate over TextFrames" dlg:checked="false"/>
  <dlg:checkbox dlg:id="quick" dlg:tab-index="0" dlg:left="20" dlg:top="89" dlg:width="111" dlg:height="10" dlg:help-text="Temporary placeholders instead of iterating over newline breaks. (Only tested with Google Translate. Might screw up others.)" dlg:help-url="HIDID" dlg:value="quick paragraph linebreak handling" dlg:checked="false"/>
  <dlg:checkbox dlg:id="debug" dlg:tab-index="3" dlg:left="20" dlg:top="134" dlg:width="100" dlg:height="10" dlg:help-text="Log file in /tmp/pagetranslate-libreoffice.txt" dlg:help-url="HIDID" dlg:value="additonal debugging" dlg:checked="true"/>
  <dlg:textfield dlg:id="cmd" dlg:tab-index="6" dlg:left="172" dlg:top="74" dlg:width="75" dlg:height="10" dlg:help-text="Command to run, use `{}` as placeholder for text section" dlg:help-url="HIDID"/>
  <dlg:textfield dlg:id="email" dlg:tab-index="5" dlg:left="172" dlg:top="52" dlg:width="75" dlg:height="10" dlg:help-text="MyMemory asks for an email addres (does not require it)" dlg:help-url="HIDID"/>
  <dlg:checkbox dlg:id="slow" dlg:tab-index="2" dlg:left="20" dlg:top="119" dlg:width="109" dlg:height="10" dlg:help-text="Split sentences on formatting prior translation (= more roundtrips, less cohesive sentence structure / translation)" dlg:help-url="HIDID" dlg:value="slow mode (more inline formatting)" dlg:checked="false"/>
  <dlg:fixedline dlg:id="FixedLine1" dlg:tab-index="7" dlg:left="15" dlg:top="76" dlg:width="110" dlg:height="8" dlg:value="Options"/>
  <dlg:fixedline dlg:id="FixedLine3" dlg:tab-index="8" dlg:left="15" dlg:top="15" dlg:width="110" dlg:height="8" dlg:value="Service"/>
  <dlg:fixedline dlg:id="FixedLine2" dlg:tab-index="9" dlg:left="137" dlg:top="15" dlg:width="110" dlg:height="8" dlg:value="Parameters"/>
  <dlg:fixedline dlg:id="Label1" dlg:tab-index="10" dlg:left="140" dlg:top="30" dlg:width="23" dlg:height="8" dlg:value="API key "/>
  <dlg:fixedline dlg:id="Label2" dlg:tab-index="11" dlg:left="140" dlg:top="52" dlg:width="28" dlg:height="8" dlg:printable="false" dlg:value="Email adr "/>
  <dlg:fixedline dlg:id="Label3" dlg:tab-index="12" dlg:left="140" dlg:top="74" dlg:width="30" dlg:height="8" dlg:value="Command "/>
  <dlg:menulist dlg:id="backend" dlg:tab-index="13" dlg:left="20" dlg:top="30" dlg:width="105" dlg:height="14" dlg:help-text="Which translation service to use. (Some might require an API key, or email address.)" dlg:spin="true" dlg:linecount="12">
   <dlg:menupopup>
    <dlg:menuitem dlg:value="Google Translate"/>



    <dlg:menuitem dlg:value="MyMemory"/>
    <dlg:menuitem dlg:value="command line tool"/>
    <dlg:menuitem dlg:value="DeepL API"/>
    <dlg:menuitem dlg:value="DeepL Free API"/>
    <dlg:menuitem dlg:value="DeepL web interface"/>
    <dlg:menuitem dlg:value="Microsoft Translator"/>

    <dlg:menuitem dlg:value="QCRI"/>
    <dlg:menuitem dlg:value="Pons"/>




    <dlg:menuitem dlg:value="Linguee"/>
    <dlg:menuitem dlg:value="Yandex"/>
   </dlg:menupopup>


  </dlg:menulist>



  <dlg:fixedline dlg:style-id="0" dlg:id="Label4" dlg:tab-index="14" dlg:left="173" dlg:top="42" dlg:width="65" dlg:height="6" dlg:value="for DeepL/Microsoft/QCRI/etc."/>
  <dlg:fixedline dlg:style-id="0" dlg:id="Label5" dlg:tab-index="15" dlg:left="173" dlg:top="64" dlg:width="29" dlg:height="6" dlg:value="for MyMemory"/>
  <dlg:fixedline dlg:style-id="0" dlg:id="Label6" dlg:tab-index="16" dlg:left="172" dlg:top="87" dlg:width="50" dlg:height="6" dlg:value="CLI program + arguments"/>
  <dlg:fixedline dlg:style-id="0" dlg:id="Label7" dlg:tab-index="17" dlg:left="22" dlg:top="48" dlg:width="104" dlg:height="7" dlg:help-url="vnd.sun.star.help://help/vnd.include-once.pagetranslate/config.xhp?Language=en&amp;System=UNIX&amp;UseDB=no" dlg:value="See                    on how the translation services             &#x0a;"/>
  <dlg:fixedline dlg:style-id="0" dlg:id="FixedLine4" dlg:tab-index="18" dlg:left="22" dlg:top="55" dlg:width="104" dlg:height="7" dlg:help-url="vnd.sun.star.help://help/vnd.include-once.pagetranslate/config.xhp?Language=en&amp;System=UNIX&amp;UseDB=no" dlg:value="differ in behaviour and which options may apply.         "/>
  <dlg:fixedline dlg:style-id="0" dlg:id="FixedLine5" dlg:tab-index="19" dlg:left="22" dlg:top="62" dlg:width="104" dlg:height="7" dlg:help-url="vnd.sun.star.help://help/vnd.include-once.pagetranslate/config.xhp?Language=en&amp;System=UNIX&amp;UseDB=no" dlg:value="Some do require an additional Python extension.               &#x0a;"/>
  <dlg:fixedline dlg:style-id="1" dlg:id="FixedLine6" dlg:tab-index="20" dlg:left="31" dlg:top="48" dlg:width="22" dlg:height="7" dlg:help-url="vnd.sun.star.help://help/vnd.include-once.pagetranslate/config.xhp?Language=en&amp;System=UNIX&amp;UseDB=no" dlg:value="help pages"/>
 </dlg:bulletinboard>
</dlg:window>

Changes to OptionsSchema.xcs.

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
  <info>
    <author></author>
    <desc>List of config options for pagetranslate.</desc>
  </info>
  <templates>
    <group oor:name="Leaf">
      <info><desc>The data for one leaf. Works without, so not relevant for storage. Purpose unclear.</desc></info>
      <!--prop oor:name="String0" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String1" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String2" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String3" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String4" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String5" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String6" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String7" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String8" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String9" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String10" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String11" oor:type="xs:string"><value></value></prop-->
    </group>
  </templates>
  <component>
    <group oor:name="Leaves">
      <group oor:name="Flags" oor:type="Leaf">
        <prop oor:name="google" oor:type="xs:short"><value>1</value></prop>
        <prop oor:name="deepl_web" oor:type="xs:short"><value>0</value></prop>
        <prop oor:name="deepl_api" oor:type="xs:short"><value>0</value></prop>
        <prop oor:name="api_key" oor:type="xs:string"><value></value></prop>
        <prop oor:name="microsoft" oor:type="xs:short"><value>0</value></prop>
        <prop oor:name="mymemory" oor:type="xs:short"><value>0</value></prop>
        <prop oor:name="email" oor:type="xs:string"><value></value></prop>
        <prop oor:name="cli" oor:type="xs:short"><value>0</value></prop>
        <prop oor:name="cmd" oor:type="xs:string"><value>translate-cli -o -f auto -t {lang} {text}</value></prop>
        <prop oor:name="quick" oor:type="xs:short"><value>0</value></prop>
        <prop oor:name="frames" oor:type="xs:short"><value>0</value></prop>
        <prop oor:name="slow" oor:type="xs:short"><value>0</value></prop>
        <prop oor:name="debug" oor:type="xs:short"><value>1</value></prop>
      </group>
    </group>
  </component>
</oor:component-schema>







|





|
<
<
<
<
<





|
<
<

<
<

<









11
12
13
14
15
16
17
18
19
20
21
22
23
24





25
26
27
28
29
30


31


32

33
34
35
36
37
38
39
40
41
  <info>
    <author></author>
    <desc>List of config options for pagetranslate.</desc>
  </info>
  <templates>
    <group oor:name="Leaf">
      <info><desc>The data for one leaf. Works without, so not relevant for storage. Purpose unclear.</desc></info>
   <!--prop oor:name="String0" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String1" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String2" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String3" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String4" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String5" oor:type="xs:string"><value></value></prop>
      <prop oor:name="String6" oor:type="xs:string"><value></value></prop-->





    </group>
  </templates>
  <component>
    <group oor:name="Leaves">
      <group oor:name="Flags" oor:type="Leaf">
        <prop oor:name="backend" oor:type="xs:string"><value>Google Translate</value></prop>


        <prop oor:name="api_key" oor:type="xs:string"><value></value></prop>


        <prop oor:name="email" oor:type="xs:string"><value></value></prop>

        <prop oor:name="cmd" oor:type="xs:string"><value>translate-cli -o -f auto -t {lang} {text}</value></prop>
        <prop oor:name="quick" oor:type="xs:short"><value>0</value></prop>
        <prop oor:name="frames" oor:type="xs:short"><value>0</value></prop>
        <prop oor:name="slow" oor:type="xs:short"><value>0</value></prop>
        <prop oor:name="debug" oor:type="xs:short"><value>1</value></prop>
      </group>
    </group>
  </component>
</oor:component-schema>

Changes to pagetranslate.py.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/python
# encoding: utf-8
# api: uno
# type: callback
# category: language
# title: PageTranslate
# description: Action button to get whole Writer document translated
# version: 1.6.90
# state: beta
# author: mario
# url: https://fossil.include-once.org/pagetranslate/
# depends: python:requests (>= 2.5), python:uno
# pack: *.py, pythonpath/*.py, META-INF/*, pkg-desc, *.x*, icons/*
# license: GNU LGPL 2.1
# forked-from: TradutorLibreText (Claudemir de Almeida Rosa)







|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/python
# encoding: utf-8
# api: uno
# type: callback
# category: language
# title: PageTranslate
# description: Action button to get whole Writer document translated
# version: 1.7.1
# state: beta
# author: mario
# url: https://fossil.include-once.org/pagetranslate/
# depends: python:requests (>= 2.5), python:uno
# pack: *.py, pythonpath/*.py, META-INF/*, pkg-desc, *.x*, icons/*
# license: GNU LGPL 2.1
# forked-from: TradutorLibreText (Claudemir de Almeida Rosa)
297
298
299
300
301
302
303

304
305
306


307
308
309
310
311
312
313
        except:
            log.error(format_exc())
        return True
    # deal with CheckBox/TextEdit control differences
    def getControlValue(self, c):
        if hasattr(c, "State"): return int(1 if c.State else 0)
        elif hasattr(c, "Text"): return str(c.Text)

    def setControlValue(self, c, value):
        if hasattr(c, "State"): c.State = int(value if value else 0)
        elif hasattr(c, "Text"): c.Text = str(value if value else "")


   

    # XContainerWindowEventHandler
    def getSupportedMethodNames(self): return ("external_event",)
    # XServiceInfo
    def supportsService(self, name): return (name == self.impl_id)
    def getImplementationName(self): return self.impl_id







>



>
>







297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
        except:
            log.error(format_exc())
        return True
    # deal with CheckBox/TextEdit control differences
    def getControlValue(self, c):
        if hasattr(c, "State"): return int(1 if c.State else 0)
        elif hasattr(c, "Text"): return str(c.Text)
        elif hasattr(c, "getSelectedItem"): return str(c.getSelectedItem())
    def setControlValue(self, c, value):
        if hasattr(c, "State"): c.State = int(value if value else 0)
        elif hasattr(c, "Text"): c.Text = str(value if value else "")
        elif hasattr(c, "selectItem"): c.selectItem(str(value if value else ""), True)
        #else: log.debug([c, dir(c)])
   

    # XContainerWindowEventHandler
    def getSupportedMethodNames(self): return ("external_event",)
    # XServiceInfo
    def supportsService(self, name): return (name == self.impl_id)
    def getImplementationName(self): return self.impl_id

Changes to pythonpath/translationbackends.py.

63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
        return text

    # decode HTML entities
    def html_unescape(self, s):
        try:
            return html.unescape(s)
        except:
            import HTMLParser
            return HTMLParser.HTMLParser().unescape(s)

    # iterate over text segments (1900 char limit)        
    def translate(self, text, lang="auto"):
        if lang == "auto":
            lang = self.params["lang"]
        #log.debug("translate %d chars" % len(text))
        if len(text) < 2:







|
<







63
64
65
66
67
68
69
70

71
72
73
74
75
76
77
        return text

    # decode HTML entities
    def html_unescape(self, s):
        try:
            return html.unescape(s)
        except:
            return s.replace("&#39;", "'").replace("&amp;", "&").replace("&lt;", "<").replace("&gt;", ">").replace("&quot;", '"')


    # iterate over text segments (1900 char limit)        
    def translate(self, text, lang="auto"):
        if lang == "auto":
            lang = self.params["lang"]
        #log.debug("translate %d chars" % len(text))
        if len(text) < 2:
446
447
448
449
450
451
452
453
454
455



456
457
458
459

460



461
462
463
464

465
        }
        for k,v in repl.items():
            if re.match("""^["']?[\{%$]" + k + "[\}%$]?["']?$""", arg):
                return v
        return arg

# maps a pagetranslate.t.* object (in main module),
# according to config dict {"goog":1, "deepl":0}
def assign_service(params):
    if params.get("deepl_web"):    return deepl_web(params)



    elif params.get("deepl_api"):  return deepl_api(params)
    elif params.get("deepl_free"): return deepl_free_api(params)
    elif params.get("mymemory"):   return mymemory(params)
    elif params.get("translate_python") or params.get("microsoft") or params.get("mymemory"):

        return translate_python(params)



    elif params.get("cli"):
        return cli(params)
    else:
        return google(params)









|

|
>
>
>
|
|
|
<
>
|
>
>
>
|
|

|
>

445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460

461
462
463
464
465
466
467
468
469
470
471
        }
        for k,v in repl.items():
            if re.match("""^["']?[\{%$]" + k + "[\}%$]?["']?$""", arg):
                return v
        return arg

# maps a pagetranslate.t.* object (in main module),
# according to configured backend (now a string)
def assign_service(params):
    w = params.get("backend", "Google")
    map = {
        "^google": google,
        "^deepl [\s_] web": deepl_web,
        "^deepl [\s_] (api|pro)": deepl_api,
        "^deepl \s free": deepl_free_api,
        "^mymemory | translated\.net": mymemory,

        "^command | ^CLI | tool | program": cli,
        "^microsoft | translate[_-]py": translate_python,
        "^linguee | ^pons | QCRI | yandex": deep_translator,
    }
    for rx, cls in map.items():
        if re.match(rx, w, re.I|re.X):
            break
    else:
        cls = google
    return cls(params)