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

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


Check-in [5ea14334ba]

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

Overview
Comment:Add ArgosTranslate (offline/python-based backend), albeit it probably can't be used within LO`s python runtime (blocking numpy.so loading)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 5ea14334bad3e2bcc130df81067f68e1a3f92cad
User & Date: mario 2021-06-06 15:28:01
Context
2021-06-06
20:49
Simplify Argos backend, conclude that it only works with distro-LibreOffice check-in: c4b8a7b58b user: mario tags: trunk
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
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to OptionsDialog.xdl.

20
21
22
23
24
25
26

27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
  <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"/>







>




<
|
<
<







20
21
22
23
24
25
26
27
28
29
30
31

32


33
34
35
36
37
38
39
  <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="ArgosTranslate (OpenNMT)"/>
    <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="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: Microsoft Translator"/>
    <dlg:menuitem dlg:value="DT: DeepL Free API"/>
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

71
72
73
74
75


76
77
  <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"/>
  <dlg:fixedline dlg:style-id="0" dlg:id="Label7" dlg:tab-index="16" dlg:left="13" dlg:top="38" 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 help pages on how the translation services             &#x0a;"/>
  <dlg:fixedline dlg:style-id="0" dlg:id="FixedLine4" dlg:tab-index="17" dlg:left="13" dlg:top="45" 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="18" dlg:left="13" dlg:top="52" 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:id="FixedLine7" dlg:tab-index="19" dlg:left="131" dlg:top="98" dlg:width="115" dlg:height="8" dlg:value="🏴  Button "/>
  <dlg:combobox dlg:id="flag" dlg:tab-index="20" dlg:left="170" dlg:top="111" dlg:width="60" dlg:height="10" dlg:help-text="Can be any XY langauge code, 
or &quot;locale&quot; for System/Office lang, 
&quot;paragraph&quot; to honor the language toolbar, 
or &quot;select&quot; to bring up the From→To dialog" dlg:value="locale" dlg:spin="true">
   <dlg:menupopup>
    <dlg:menuitem dlg:value="locale"/>
    <dlg:menuitem dlg:value="select"/>
    <dlg:menuitem dlg:value="paragraph"/>
    <dlg:menuitem dlg:value="en"/>
    <dlg:menuitem dlg:value="mri-debug"/>
   </dlg:menupopup>
  </dlg:combobox>
  <dlg:fixedline dlg:id="FixedLine8" dlg:tab-index="21" dlg:left="137" dlg:top="112" dlg:width="27" dlg:height="8" dlg:help-text="Which language the 🏴 button defaults to" dlg:value="Language"/>
  <dlg:combobox dlg:id="cmd" dlg:tab-index="22" dlg:left="169" dlg:top="63" dlg:width="77" dlg:height="10" dlg:help-text="Command line program to pipe text snippets through.
Can use placeholders like {text}, or {lang} and {form}.
Alternatively with shell $text or %lang% syntax." dlg:value="translate-cli -o -f auto -t {lang} {text}" dlg:spin="true">
   <dlg:menupopup>
    <dlg:menuitem dlg:value="translate-cli -o -f auto -t {lang} {text}"/>
    <dlg:menuitem dlg:value="deep_translator -trans &quot;google&quot; -src &quot;auto&quot; -tg {lang} -txt {text}"/>

    <dlg:menuitem dlg:value="trans -sl {from} {text} {lang}"/>
    <dlg:menuitem dlg:value="dingonyms --en-fr {text}"/>
   </dlg:menupopup>
  </dlg:combobox>
  <dlg:img dlg:style-id="0" dlg:id="logo" dlg:tab-index="23" dlg:left="200" dlg:top="135" dlg:width="40" dlg:height="40" dlg:src="vnd.sun.star.extension://vnd.include-once.pagetranslate/icons/flags.png"/>


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







|
<
<
<









|
<
<



>





>
>


43
44
45
46
47
48
49
50



51
52
53
54
55
56
57
58
59
60


61
62
63
64
65
66
67
68
69
70
71
72
73
  <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"/>
  <dlg:fixedline dlg:style-id="0" dlg:id="Label7" dlg:tab-index="16" dlg:left="13" dlg:top="38" 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 help pages on how the translation services             &#x0a;"/>
  <dlg:fixedline dlg:style-id="0" dlg:id="FixedLine4" dlg:tab-index="17" dlg:left="13" dlg:top="45" 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="18" dlg:left="13" dlg:top="52" 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:id="FixedLine7" dlg:tab-index="19" dlg:left="131" dlg:top="98" dlg:width="115" dlg:height="8" dlg:value="🏴  Button "/>
  <dlg:combobox dlg:id="flag" dlg:tab-index="20" dlg:left="170" dlg:top="111" dlg:width="60" dlg:height="10" dlg:help-text="Can be any XY langauge code,  or &quot;locale&quot; for System/Office lang,  &quot;paragraph&quot; to honor the language toolbar,  or &quot;select&quot; to bring up the From→To dialog" dlg:value="locale" dlg:spin="true">



   <dlg:menupopup>
    <dlg:menuitem dlg:value="locale"/>
    <dlg:menuitem dlg:value="select"/>
    <dlg:menuitem dlg:value="paragraph"/>
    <dlg:menuitem dlg:value="en"/>
    <dlg:menuitem dlg:value="mri-debug"/>
   </dlg:menupopup>
  </dlg:combobox>
  <dlg:fixedline dlg:id="FixedLine8" dlg:tab-index="21" dlg:left="137" dlg:top="112" dlg:width="27" dlg:height="8" dlg:help-text="Which language the 🏴 button defaults to" dlg:value="Language"/>
  <dlg:combobox dlg:id="cmd" dlg:tab-index="22" dlg:left="169" dlg:top="63" dlg:width="77" dlg:height="10" dlg:help-text="Command line program to pipe text snippets through. Can use placeholders like {text}, or {lang} and {form}. Alternatively with shell $text or %lang% syntax." dlg:value="translate-cli -o -f auto -t {lang} {text}" dlg:spin="true">


   <dlg:menupopup>
    <dlg:menuitem dlg:value="translate-cli -o -f auto -t {lang} {text}"/>
    <dlg:menuitem dlg:value="deep_translator -trans &quot;google&quot; -src &quot;auto&quot; -tg {lang} -txt {text}"/>
    <dlg:menuitem dlg:value="argos-translate --from-lang {from} --to-lang {lang} {text}"/>
    <dlg:menuitem dlg:value="trans -sl {from} {text} {lang}"/>
    <dlg:menuitem dlg:value="dingonyms --en-fr {text}"/>
   </dlg:menupopup>
  </dlg:combobox>
  <dlg:img dlg:style-id="0" dlg:id="logo" dlg:tab-index="23" dlg:left="200" dlg:top="135" dlg:width="40" dlg:height="40" dlg:src="vnd.sun.star.extension://vnd.include-once.pagetranslate/icons/flags.png"/>
  <dlg:button dlg:id="cfg_argos" dlg:tab-index="24" dlg:left="10" dlg:top="150" dlg:width="75" dlg:height="12" dlg:value="argos config gui">
  </dlg:button>
 </dlg:bulletinboard>
</dlg:window>

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.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 }







|







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.43
# 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 }
256
257
258
259
260
261
262




263
264
265
266
267
268
269
        ctx = uno.getComponentContext()
        sm = ctx.ServiceManager
        sv = sm.createInstanceWithContext("com.sun.star.awt.Toolkit", ctx)
        myBox = sv.createMessageBox(ParentWin, MsgType, MsgButtons, MsgTitle, MsgText)
        return myBox.execute()







# Handler for settings-embedded DialogOptions.xdl window, and read/write access to our leaf in the office registry.
# (This is fairly generic/reusable, because it directly maps a dict to/from the dialog widgets.)
#
class settings(unohelper.Base, XContainerWindowEventHandler, XServiceInfo):
    impl_id = "vnd.include-once.OptionsPageTranslate"








>
>
>
>







256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
        ctx = uno.getComponentContext()
        sm = ctx.ServiceManager
        sv = sm.createInstanceWithContext("com.sun.star.awt.Toolkit", ctx)
        myBox = sv.createMessageBox(ParentWin, MsgType, MsgButtons, MsgTitle, MsgText)
        return myBox.execute()


# XActionListener for callbacks
class action_listener(unohelper.Base, XActionListener):
    def __init__(self, cb):
        self.actionPerformed = cb

# Handler for settings-embedded DialogOptions.xdl window, and read/write access to our leaf in the office registry.
# (This is fairly generic/reusable, because it directly maps a dict to/from the dialog widgets.)
#
class settings(unohelper.Base, XContainerWindowEventHandler, XServiceInfo):
    impl_id = "vnd.include-once.OptionsPageTranslate"

299
300
301
302
303
304
305





306
307
308
309
310
311
312
313
    def callHandlerMethod(self, window=".UnoDialogControl", action="initialize|ok|back", name="external_event"):
        log.debug("OptonsPageTranslate:settings.callHandlerMethod({}, {}, {})".format(repr(window), action, name))
        try:
            params = self.read()
            log.info(repr(params))
            # iterate over all dialog controls by name, and assign from/to config dict
            for name, cntrl in [(c.Model.Name, c) for c in window.getControls()]:





                if action == "initialize":
                    self.setControlValue(cntrl, params.get(name))
                elif action == "ok":
                    params[name] = self.getControlValue(cntrl)
            if action == "ok":
                self.write(params)
        except:
            log.error(format_exc())







>
>
>
>
>
|







303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
    def callHandlerMethod(self, window=".UnoDialogControl", action="initialize|ok|back", name="external_event"):
        log.debug("OptonsPageTranslate:settings.callHandlerMethod({}, {}, {})".format(repr(window), action, name))
        try:
            params = self.read()
            log.info(repr(params))
            # iterate over all dialog controls by name, and assign from/to config dict
            for name, cntrl in [(c.Model.Name, c) for c in window.getControls()]:
                log.info(name)
                if name == "cfg_argos":
                    #cntrl.setLabel("service:org.openoffice.comp.pyuno.pagetranslate?exec&cmd=argos-translate-gui")
                    cntrl.addActionListener(action_listener(lambda *x: os.system("PYTHONPATH= argos-translate-gui &")))
                    log.debug(sys.path)
                elif action == "initialize":
                    self.setControlValue(cntrl, params.get(name))
                elif action == "ok":
                    params[name] = self.getControlValue(cntrl)
            if action == "ok":
                self.write(params)
        except:
            log.error(format_exc())

Changes to pythonpath/translationbackends.py.

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
        #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)








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














>











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
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
        #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

# ArgosTranslate
#
#  Β· offline translation package (OpenNMT)
#  Β· comes with a GUI to install readymade models
#
class argos(google):

    def chpath(self):
        old = sys.path[:]
        add = ['/usr/lib/python38.zip', '/usr/lib/python3.8', '/usr/lib/python3.8/lib-dynload', '/usr/lib/python3/dist-packages']
        [sys.path.insert(0, d) for d in add]
        sys.path.append("")
        #['/opt/libreoffice7.1/program/python-core-3.8.4/lib', '/opt/libreoffice7.1/program/python-core-3.8.4/lib/lib-dynload', '/opt/libreoffice7.1/program/python-core-3.8.4/lib/lib-tk', '/opt/libreoffice7.1/program/python-core-3.8.4/lib/site-packages', '/opt/libreoffice7.1/program', '/opt/libreoffice7.1/program/python-core-3.8.4/lib/python38.zip', '/opt/libreoffice7.1/program/python-core-3.8.4/lib/python3.8', '/opt/libreoffice7.1/program/python-core-3.8.4/lib/python3.8/lib-dynload', '/home/mario/.local/lib/python3.8/site-packages', '/home/mario/projects/pluginconf', '/home/mario/projects/dingonyms', '/home/mario/projects/cookiedough', '/home/mario/projects/modseccfg/html2mallard', '/home/mario/.config/libreoffice/4/user/uno_packages/cache/uno_packages/lu16459gv1z8.tmp_/mri-1.1.2.oxt/pythonpath', '/opt/libreoffice7.1/share/extensions/dict-en/pythonpath', '/home/mario/.config/libreoffice/4/user/uno_packages/cache/uno_packages/lu877ky9aec.tmp_/pagetranslate.oxt/pythonpath']
        #['', '/usr/lib/python38.zip', '/usr/lib/python3.8', '/usr/lib/python3.8/lib-dynload', '/home/mario/.local/lib/python3.8/site-packages', '/home/mario/projects/pluginconf', '/home/mario/projects/dingonyms', '/home/mario/projects/cookiedough', '/home/mario/projects/modseccfg/html2mallard', '/usr/local/lib/python3.8/dist-packages', '/usr/lib/python3/dist-packages']
    
    def fetch(self, text, target="en", source="en"):
        self.chpath()
        from argostranslate import translate
        f, s = None, None
        for langpack in translate.get_installed_languages():
            if langpack.code == target:
                f = langpack
            elif langpack.code == source:
                s = langpack
        if not f or not s:
            raise Exception("requested language pack ("+source+"-"+target+") missing, use `argos-translate-gui` to install")
        t = s.get_translation(t)
        return t(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,
        "^argos": argos,
        "^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)