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

โŒˆโŒ‹ โŽ‡ branch:  PageTranslate


Check-in [f6fc8d0070]

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

Overview
Comment:Add Systran backend (untested).
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: f6fc8d007003922f4a8cd3cebf37da4a6c944173
User & Date: mario 2021-06-03 09:35:29
Context
2021-06-06
15:28
Add ArgosTranslate (offline/python-based backend), albeit it probably can't be used within LO`s python runtime (blocking numpy.so loading) check-in: 5ea14334ba user: mario tags: trunk
2021-06-03
09:35
Add Systran backend (untested). check-in: f6fc8d0070 user: mario tags: trunk
2021-06-02
18:08
Release as 1.9 check-in: 46ae0fdcbc user: mario tags: trunk, 1.9
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to NEWS.

1

2
3
4
5
6
7
8
2.0 (unreleased)

 * ...

1.9 (2021-06-02)
 * Add builtin PONS Text Translation backend (full text, automatic langdetect).
 * Add Google Translate Ajax API endpoint as alternative.
 * Allow behaviour configuration of secondary ๐Ÿด flag button.
 * Menu shortcut to configuration dialog working.

>







1
2
3
4
5
6
7
8
9
2.0 (unreleased)
 * Add SYSTRAN backend.
 * ...

1.9 (2021-06-02)
 * Add builtin PONS Text Translation backend (full text, automatic langdetect).
 * Add Google Translate Ajax API endpoint as alternative.
 * Allow behaviour configuration of secondary ๐Ÿด flag button.
 * Menu shortcut to configuration dialog working.

Changes to OptionsDialog.xdl.

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
  <dlg:fixedline dlg:id="FixedLine2" dlg:tab-index="8" dlg:left="131" dlg:top="5" dlg:width="115" dlg:height="8" dlg:value="Parameters"/>
  <dlg:fixedline dlg:id="Label1" dlg:tab-index="9" dlg:left="137" dlg:top="20" dlg:width="23" dlg:height="8" dlg:value="API key "/>
  <dlg:fixedline dlg:id="Label2" dlg:tab-index="10" dlg:left="137" dlg:top="42" dlg:width="28" dlg:height="8" dlg:printable="false" dlg:value="Email adr "/>
  <dlg:fixedline dlg:id="Label3" dlg:tab-index="11" dlg:left="137" dlg:top="64" dlg:width="30" dlg:height="8" dlg:value="Command "/>
  <dlg:menulist dlg:id="backend" dlg:tab-index="12" dlg:left="10" dlg:top="20" 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="20">
   <dlg:menupopup>
    <dlg:menuitem dlg:value="Google Translate"/>

    <dlg:menuitem dlg:value="MyMemory"/>
    <dlg:menuitem dlg:value="PONS Text Translation"/>
    <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="Google Ajax Translate API"/>

    <dlg:menuitem dlg:value="TP: Microsoft Translator"/>


    <dlg:menuitem dlg:value="DT: QCRI Machine Translation"/>
    <dlg:menuitem dlg:value="DT: Yandex Translation"/>
    <dlg:menuitem dlg:value="DT: Papago Web Translator"/>
    <dlg:menuitem dlg:value="DT: Pons Dictionary"/>
    <dlg:menuitem dlg:value="DT: Linguee Dictionary"/>

    <dlg:menuitem dlg:value="DT: DeepL Free API"/>
    <dlg:menuitem dlg:value="DT: Google Translate"/>
   </dlg:menupopup>
  </dlg:menulist>
  <dlg:fixedline dlg:style-id="0" dlg:id="Label4" dlg:tab-index="13" dlg:left="170" dlg:top="32" dlg:width="65" dlg:height="6" dlg:value="for DeepL / Microsoft / QCRI       "/>
  <dlg:fixedline dlg:style-id="0" dlg:id="Label5" dlg:tab-index="14" dlg:left="170" dlg:top="54" dlg:width="32" dlg:height="6" dlg:value="for MyMemory  "/>
  <dlg:fixedline dlg:style-id="0" dlg:id="Label6" dlg:tab-index="15" dlg:left="170" dlg:top="77" dlg:width="50" dlg:height="6" dlg:value="CLI program + arguments"/>







>






|
>
|
>
>





>







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
  <dlg:fixedline dlg:id="FixedLine2" dlg:tab-index="8" dlg:left="131" dlg:top="5" dlg:width="115" dlg:height="8" dlg:value="Parameters"/>
  <dlg:fixedline dlg:id="Label1" dlg:tab-index="9" dlg:left="137" dlg:top="20" dlg:width="23" dlg:height="8" dlg:value="API key "/>
  <dlg:fixedline dlg:id="Label2" dlg:tab-index="10" dlg:left="137" dlg:top="42" dlg:width="28" dlg:height="8" dlg:printable="false" dlg:value="Email adr "/>
  <dlg:fixedline dlg:id="Label3" dlg:tab-index="11" dlg:left="137" dlg:top="64" dlg:width="30" dlg:height="8" dlg:value="Command "/>
  <dlg:menulist dlg:id="backend" dlg:tab-index="12" dlg:left="10" dlg:top="20" 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="20">
   <dlg:menupopup>
    <dlg:menuitem dlg:value="Google Translate"/>
    <dlg:menuitem dlg:value="GoogleApis Ajax Translate"/>
    <dlg:menuitem dlg:value="MyMemory"/>
    <dlg:menuitem dlg:value="PONS Text Translation"/>
    <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="SYSTRAN translate Pro API"/>
 <!--dlg:menuitem dlg:value="IBM Watson API"/-->
    <dlg:menuitem dlg:value="TP: Microsoft Translator"/> <!-- might remove -->
 <!--dlg:menuitem dlg:value="TP: GoogleT"/-->
 <!--dlg:menuitem dlg:value="TP: MyMemory"/-->
    <dlg:menuitem dlg:value="DT: QCRI Machine Translation"/>
    <dlg:menuitem dlg:value="DT: Yandex Translation"/>
    <dlg:menuitem dlg:value="DT: Papago Web Translator"/>
    <dlg:menuitem dlg:value="DT: Pons Dictionary"/>
    <dlg:menuitem dlg:value="DT: Linguee Dictionary"/>
    <dlg:menuitem dlg:value="DT: Microsoft Translator"/>
    <dlg:menuitem dlg:value="DT: DeepL Free API"/>
    <dlg:menuitem dlg:value="DT: Google Translate"/>
   </dlg:menupopup>
  </dlg:menulist>
  <dlg:fixedline dlg:style-id="0" dlg:id="Label4" dlg:tab-index="13" dlg:left="170" dlg:top="32" dlg:width="65" dlg:height="6" dlg:value="for DeepL / Microsoft / QCRI       "/>
  <dlg:fixedline dlg:style-id="0" dlg:id="Label5" dlg:tab-index="14" dlg:left="170" dlg:top="54" dlg:width="32" dlg:height="6" dlg:value="for MyMemory  "/>
  <dlg:fixedline dlg:style-id="0" dlg:id="Label6" dlg:tab-index="15" dlg:left="170" dlg:top="77" dlg:width="50" dlg:height="6" dlg:value="CLI program + arguments"/>

Changes to description.xml.

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<description xmlns="http://openoffice.org/extensions/description/2006" xmlns:dep="http://openoffice.org/extensions/description/2006" xmlns:xlink="http://www.w3.org/1999/xlink">
  <identifier value="vnd.include-once.pagetranslate"/>
  <version value="1.9"/>
  <display-name>
    <name lang="en">PageTranslate</name>
  </display-name>
  <dependencies>
    <OpenOffice.org-minimal-version value="3.0" dep:name="OpenOffice.org 3.0"/>
  </dependencies>
  <registration>



|







1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<description xmlns="http://openoffice.org/extensions/description/2006" xmlns:dep="http://openoffice.org/extensions/description/2006" xmlns:xlink="http://www.w3.org/1999/xlink">
  <identifier value="vnd.include-once.pagetranslate"/>
  <version value="1.9.8"/>
  <display-name>
    <name lang="en">PageTranslate</name>
  </display-name>
  <dependencies>
    <OpenOffice.org-minimal-version value="3.0" dep:name="OpenOffice.org 3.0"/>
  </dependencies>
  <registration>

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.9
# 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/*
# config:
#    { name: frames, type: bool, value: 0, description: traverse TextFrames }







|







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.9.8
# 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/*
# config:
#    { name: frames, type: bool, value: 0, description: traverse TextFrames }

Changes to pythonpath/translationbackends.py.

404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423


424
425
426
427
428
429
430
...
580
581
582
583
584
585
586




























587
588
589
590
591
592
593
594
595
596
597
598
599

600
601
602
603
604
605
606
607
608
609
610
            "api_key": params["api_key"]
        }
        # import
        import functools
        import deep_translator
        # map to backends / uniform decorators
        backend = [
            id for id in ["linguee", "pons", "QCRI", "yandex", "deepl", "free", "papago"] if re.search(id, backend, re.I)
        ]
        log.info(backend)
        if "linguee" in backend:
            self.translate = self.from_words(deep_translator.LingueeTranslator(**langs).translate)
        elif "pons" in backend:
            self.translate = self.from_words(deep_translator.PonsTranslator(**langs).translate)
        elif "QCRI" in backend:
            self.translate = functools.partial(deep_translator.QCRI(**api_key).translate, **langs)
        elif "yandex" in backend:
            self.translate = functools.partial(deep_translator.YandexTranslator(**api_key).translate, **langs)
        elif "deepl" in backend:
            self.translate = deep_translator.DeepL(api_key=params["api_key"], use_free_api=("free" in backend), **langs).translate


        elif "papago" in backend:
            client_id, secret_key = params["api_key"].split(":") # api_key must contain `clientid:clientsecret`
            self.translate = deep_translator.PapagoTranslator(client_id=client_id, secret_key=secret_key, **langs).translate
        else:
            self.translate = deep_translator.GoogleTranslator(**langs).translate

    # shorten language co-DE to just two-letter moniker
................................................................................

    # invoked once to get session identifier        
    def impressionId(self):
        html = http.get(self.init_url).text
        return re.findall(""" ["']?impressionId["']? \s*[:=]\s* ["'](\w+-[\w-]+-\w+)["'] """, html, re.X)[0]































# 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 [\s\-_] translate$": google,
        "^google.*ajax": google_ajax,
        "^deepl [\s_] web": deepl_web,
        "^deepl [\s_] (api|pro)": deepl_api,
        "^deepl \s free": deepl_free_api,
        "^mymemory | translated\.net": mymemory,
        "^pons \s text": pons,

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








|












>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>













>











404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
...
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
            "api_key": params["api_key"]
        }
        # import
        import functools
        import deep_translator
        # map to backends / uniform decorators
        backend = [
            id for id in ["linguee", "pons", "QCRI", "yandex", "deepl", "free", "microsoft", "papago"] if re.search(id, backend, re.I)
        ]
        log.info(backend)
        if "linguee" in backend:
            self.translate = self.from_words(deep_translator.LingueeTranslator(**langs).translate)
        elif "pons" in backend:
            self.translate = self.from_words(deep_translator.PonsTranslator(**langs).translate)
        elif "QCRI" in backend:
            self.translate = functools.partial(deep_translator.QCRI(**api_key).translate, **langs)
        elif "yandex" in backend:
            self.translate = functools.partial(deep_translator.YandexTranslator(**api_key).translate, **langs)
        elif "deepl" in backend:
            self.translate = deep_translator.DeepL(api_key=params["api_key"], use_free_api=("free" in backend), **langs).translate
        elif "microsoft" in backend:
            self.translate = deep_translator.MicrosoftTranslator(api_key=params["api_key"], **langs).translate
        elif "papago" in backend:
            client_id, secret_key = params["api_key"].split(":") # api_key must contain `clientid:clientsecret`
            self.translate = deep_translator.PapagoTranslator(client_id=client_id, secret_key=secret_key, **langs).translate
        else:
            self.translate = deep_translator.GoogleTranslator(**langs).translate

    # shorten language co-DE to just two-letter moniker
................................................................................

    # invoked once to get session identifier        
    def impressionId(self):
        html = http.get(self.init_url).text
        return re.findall(""" ["']?impressionId["']? \s*[:=]\s* ["'](\w+-[\w-]+-\w+)["'] """, html, re.X)[0]


# SYSTRAN Translate API
# ยท https://docs.systran.net/translateAPI/translation/
# ยท also requires an API key (seemingly not available in trial subscription)
#
class systran(google):
    url = "https://api-translate.systran.net/translation/text/translate?key=YOUR_API_KEY&input=&target=&source="
    #url = "/compatmode/google/language/translate/v2?q=..&target=lang"
    def fetch(self, text, target="en", source="auto"):
        r = http.post(
            url=self.url,
            params={
                "q": text,
                "target": target,
                "source": source,
                #"key": self.params["api_key"],
            },
            headers={
                "Authorization": "Bearer " + self.params["api_key"]
            }
        )
        data = r.json()   # if not JSON response, we probably ran into a HTTP/API error
        #log.debug(repr(data))
        if data.get("error"):
            raise ConnectionRefusedError(data["error"], r.status_code, r.headers) 
        else:
            text = data["outputs"][0]["output"]  # nested result structure
        return text


# 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 [\s\-_] translate$": google,
        "^google.*ajax": google_ajax,
        "^deepl [\s_] web": deepl_web,
        "^deepl [\s_] (api|pro)": deepl_api,
        "^deepl \s free": deepl_free_api,
        "^mymemory | translated\.net": mymemory,
        "^pons \s text": pons,
        "^systran": systran,
        "^command | ^CLI | tool | program": cli,
        "^microsoft | translate[_-]py | ^T-?P: | \(T-?P\)": translate_python,
        "linguee | pons\sdict | QCRI | yandex | ^D-?T: | \(D-?T\)": deep_translator,
    }
    for rx, cls in map.items():
        if re.search(rx, w, re.I|re.X):
            break
    else:
        cls = google
    return cls(params)