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

⌈⌋ branch:  PageTranslate


Check-in [cfa194ff57]

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

Overview
Comment:test and fix systran and deepl, jettison deepl pro/api split (now decided on key suffix)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: cfa194ff57da0e7441a0772de824173e3d1e8980
User & Date: mario 2022-10-21 04:49:03
Context
2022-10-21
06:20
basic system tests check-in: 66f8ffe0f8 user: mario tags: trunk
04:49
test and fix systran and deepl, jettison deepl pro/api split (now decided on key suffix) check-in: cfa194ff57 user: mario tags: trunk
04:47
shorten ctx usage, but inject to pt_dialogs check-in: ab7fa5386f user: mario tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

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
43
  <dlg:textfield dlg:id="email" dlg:tab-index="6" dlg:left="169" dlg:top="42" dlg:width="75" dlg:height="10" dlg:help-text="MyMemory asks for an email addres (does not require it)" dlg:help-url="HIDID"/>
  <dlg:fixedline dlg:id="FixedLine1" dlg:tab-index="6" dlg:left="5" dlg:top="75" dlg:width="117" dlg:height="9" dlg:value="Options"/>
  <dlg:fixedline dlg:id="FixedLine3" dlg:tab-index="7" dlg:left="5" dlg:top="5" dlg:width="117" dlg:height="7" dlg:value="Service"/>
  <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="DuckDuckGo (MsftTr)"/>
    <dlg:menuitem dlg:value="command line tool"/>
    <dlg:menuitem dlg:value="ArgosTranslate (OpenNMT)"/>
    <dlg:menuitem dlg:value="LibreTranslate"/>
    <dlg:menuitem dlg:value="LibreLocal http://localhost:5000/translate"/>
    <dlg:menuitem dlg:value="DeepL web interface"/>
    <dlg:menuitem dlg:value="DeepL Free API"/>
    <dlg:menuitem dlg:value="DeepL API"/>
    <dlg:menuitem dlg:value="SYSTRAN translate Pro API"/>
    <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"/>







|





|





|
<







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:textfield dlg:id="email" dlg:tab-index="6" dlg:left="169" dlg:top="42" dlg:width="75" dlg:height="10" dlg:help-text="MyMemory asks for an email addres (does not require it)" dlg:help-url="HIDID"/>
  <dlg:fixedline dlg:id="FixedLine1" dlg:tab-index="6" dlg:left="5" dlg:top="75" dlg:width="117" dlg:height="9" dlg:value="Options"/>
  <dlg:fixedline dlg:id="FixedLine3" dlg:tab-index="7" dlg:left="5" dlg:top="5" dlg:width="117" dlg:height="7" dlg:value="Service"/>
  <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. Google Ajax, DuckDuckGo, PONS Web, or Argos Translate are recommended defaults. For sensitive documents, DeepL, SysTran, or a Microsoft subscription might be more suitable." 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="DuckDuckGo"/>
    <dlg:menuitem dlg:value="command line tool"/>
    <dlg:menuitem dlg:value="ArgosTranslate (OpenNMT)"/>
    <dlg:menuitem dlg:value="LibreTranslate"/>
    <dlg:menuitem dlg:value="LibreLocal http://localhost:5000/translate"/>
    <dlg:menuitem dlg:value="DeepL web interface"/>
    <dlg:menuitem dlg:value="DeepL Pro/Free API"/>

    <dlg:menuitem dlg:value="SYSTRAN translate Pro API"/>
    <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"/>

Changes to pythonpath/translationbackends.py.

226
227
228
229
230
231
232
233
234
235
236
237

238
239
240
241
242
243
244
#
#  · calls mobile page http://translate.google.com/m?hl=en&sl=auto&q=TRANSLATE
#  · iterates over each 1900 characters
#
class GoogleWeb(BackendUtils):
    """ broadest language support (130) """

    match = r"^google$ | ^google [\s\-_] (translate|web)$"
    raises_error = False
    requires_key = False
    lang_detect = "auto"
    is_tested = 1.0  # main backend, well tested


    # request text translation from google
    def fetch(self, text):
        dst_lang = self.params["lang"]
        src_lang = self.params["from"] # "auto" works
        # fetch translation page
        url = "https://translate.google.com/m?tl=%s&hl=%s&sl=%s&q=%s" % (







|




>







226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
#
#  · calls mobile page http://translate.google.com/m?hl=en&sl=auto&q=TRANSLATE
#  · iterates over each 1900 characters
#
class GoogleWeb(BackendUtils):
    """ broadest language support (130) """

    match = r"^google$ | ^google [\s\-_]* (translate|web)$"
    raises_error = False
    requires_key = False
    lang_detect = "auto"
    is_tested = 1.0  # main backend, well tested
    max_len = 1900

    # request text translation from google
    def fetch(self, text):
        dst_lang = self.params["lang"]
        src_lang = self.params["from"] # "auto" works
        # fetch translation page
        url = "https://translate.google.com/m?tl=%s&hl=%s&sl=%s&q=%s" % (
261
262
263
264
265
266
267

268
269
270
271
272
273
274
    """ alternative/faster interface """

    match = r"^google.*ajax"
    raises_error = False
    requires_key = False
    lang_detect = "auto"
    is_tested = 0.9  # main backend


    # request text translation from google
    def fetch(self, text):
        resp = http.get(
            url="https://translate.googleapis.com/translate_a/single",
            params={
                "client": "gtx",







>







262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
    """ alternative/faster interface """

    match = r"^google.*ajax"
    raises_error = False
    requires_key = False
    lang_detect = "auto"
    is_tested = 0.9  # main backend
    max_len = 1900

    # request text translation from google
    def fetch(self, text):
        resp = http.get(
            url="https://translate.googleapis.com/translate_a/single",
            params={
                "client": "gtx",
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
        raise ConnectionRefusedError(resp, resp.content)


# DeepL online translator
#  · will easily yield HTTP 429 Too many requests,
#    so probably not useful for multi-paragraph translation anyway (just text selections)
#  · a convoluted json-rpc interface (but hints at context-sensitive language recognition)
#  · mostly here for testing if DeepL yields better results for your text documents,
#    before going through the registration attempt hassle
#
# data origins:
#  · https://www.deepl.com/translator = nothing
#  · jsonrpcId = random integer
#  · sessionId = random client-side guid
#      (https://www.deepl.com/js/translator_glossary_late.min.js?v=… → generated in `function u()`)
#  · instanceId
#      (https://www.deepl.com/PHP/backend/clientState.php?request_type=jsonrpc&il=EN → "uid":"(.+=)")
#  · LMTBID cookie
#      (https://s.deepl.com/web/stats?request_type=jsonrpc ← jsonrpc+session+instId+clientinfos)
#
# translation requests:
#  < https://www2.deepl.com/jsonrpc
#    cookies: LMTBID: GUID...
#    referer: https://www.deepl.com/translator
# repsonse  body:
#  > result.translations[0].beams[0].postprocessed_sentence
#
class DeeplWeb(BackendUtils):
    """ 15 langs, screen scraping access """

    match = r"^deepl [\s_\-]* web"
    requires_key = False
    raises_error = True







|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







467
468
469
470
471
472
473
474


















475
476
477
478
479
480
481
        raise ConnectionRefusedError(resp, resp.content)


# DeepL online translator
#  · will easily yield HTTP 429 Too many requests,
#    so probably not useful for multi-paragraph translation anyway (just text selections)
#  · a convoluted json-rpc interface (but hints at context-sensitive language recognition)
#  · mostly here for testing if DeepL yields better results for your text documents


















#
class DeeplWeb(BackendUtils):
    """ 15 langs, screen scraping access """

    match = r"^deepl [\s_\-]* web"
    requires_key = False
    raises_error = True
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
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710


# DeepL API
#
# So, there's a free API and the pro API now. This might make the _web scraping
# dancearound redundant. The free API is certainly more enticing for testing.
# In general, DeepL provides a more streamlined translation than GoogleWeb.
# It's mostly in here because the API is quite simple. *ENTIRELY UNTESTED*
#
class DeeplApi(DeeplWeb):
    """ High quality AI translation, 15 langs"""

    match = r"^deepl [\s_\-]* (api|pro)"
    requires_key = True
    raises_error = True
    lang_detect = "auto"
    is_tested = 0.0  # no key

    api_url = "https://api.deepl.com/v2/translate"

    headers = {}

    def __init__(self, **params):
        DeeplWeb.__init__(self, **params)
        self.headers = {
            "Authorization": "DeepL-Auth-Key " + self.params["api_key"],
        }




    def translate(self, text, preserve=0): # pylint: disable=arguments-differ

        # https://www.deepl.com/docs-api/translating-text/request/
        params = {
            "text": text,
            "target_lang": self.params["lang"],

            "split_sentences": "1",
            "preserve_formatting": str(preserve),
            "formality": "default",
            #"tag_handling": "xml|html",
        }
        if self.params["lang"] != "auto":
            params["source_lang"] = self.params["lang"]


        resp = http.post(
            self.api_url,
            params=params,
            headers=self.headers,
        )

        if resp.status_code == 200:
            resp = resp.json().get("translations")
            if resp:
                return resp[0]["text"]
        else:
            self.log.error(repr(resp))
            if resp.status_code == 403:
                resp.status = "Authorization/API key invalid"
            if not hasattr(resp, "status"):
                resp.status = "???"
            raise ConnectionRefusedError(resp.status_code, resp.status, resp.headers)
        return text

    def linebreakwise(self, text):
        return self.translate(text, preserve=1)


# DeepL free API
#
# Registration is broken (error 10040 or whatever, "contact support" lel), even though
# it seems to create an account regardless; but API yields SSL or connection errors.
# Thus STILL UNTESTED.
#
class DeeplFree(DeeplApi):
    """ 15 langs, AI translation, free keys? """

    match = r"^deepl [\s_\-]* free"
    requires_key = True
    raises_error = True
    lang_detect = "auto"
    is_tested = 0.0  # no key
    api_url = "https://api-free.deepl.com/v2/translate"
    headers = {}

    def __init__(self, **params):
        DeeplApi.__init__(self, **params)
        self.headers = {}


# deep-translator
# requires `pip install deep-translator`
#  · more backends than pytranslate,
#    though PONS+Linguee are just dictionaries
#  → https://github.com/nidhaloff/deep-translator
#







|




|



|
>

>







>
>
>







>





<
<

>


|


>
















<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







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






















672
673
674
675
676
677
678


# DeepL API
#
# So, there's a free API and the pro API now. This might make the _web scraping
# dancearound redundant. The free API is certainly more enticing for testing.
# In general, DeepL provides a more streamlined translation than GoogleWeb.
# It's mostly in here because the API is quite simple. (Rudimentarily tested)
#
class DeeplApi(DeeplWeb):
    """ High quality AI translation, 15 langs"""

    match = r"^deepl (free|translate|api|pro[\s/_-])*"
    requires_key = True
    raises_error = True
    lang_detect = "auto"
    is_tested = 0.1  # only free ever tested
    max_len = 50000
    api_url = "https://api.deepl.com/v2/translate"
    api_free = "https://api-free.deepl.com/v2/translate"
    headers = {}

    def __init__(self, **params):
        DeeplWeb.__init__(self, **params)
        self.headers = {
            "Authorization": "DeepL-Auth-Key " + self.params["api_key"],
        }
        # confirmed: if key ends in :fx - https://www.deepl.com/docs-api/api-access/general-information/
        if re.search(r":fx\s*$", self.params["api_key"], re.I):
            self.api_url = self.api_free

    def translate(self, text, preserve=0): # pylint: disable=arguments-differ

        # https://www.deepl.com/docs-api/translating-text/request/
        params = {
            "text": text,
            "target_lang": self.params["lang"],
            "source_lang": self.non_auto_lang(None), # from if not 'auto'
            "split_sentences": "1",
            "preserve_formatting": str(preserve),
            "formality": "default",
            #"tag_handling": "xml|html",
        }



        self.log.debug("p=%s h=%s", params, self.headers)
        resp = http.post(
            self.api_url,
            data=params,
            headers=self.headers,
        )
        self.log.debug(resp)
        if resp.status_code == 200:
            resp = resp.json().get("translations")
            if resp:
                return resp[0]["text"]
        else:
            self.log.error(repr(resp))
            if resp.status_code == 403:
                resp.status = "Authorization/API key invalid"
            if not hasattr(resp, "status"):
                resp.status = "???"
            raise ConnectionRefusedError(resp.status_code, resp.status, resp.headers)
        return text

    def linebreakwise(self, text):
        return self.translate(text, preserve=1)
























# deep-translator
# requires `pip install deep-translator`
#  · more backends than pytranslate,
#    though PONS+Linguee are just dictionaries
#  → https://github.com/nidhaloff/deep-translator
#
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
            if re.match(r"""^["']?[\{%$]""" + key + r"""[\}%$]?["']?$""", arg):
                return value
        return arg


# SYSTRAN Translate API
# · https://docs.systran.net/translateAPI/translation/
# · also requires an API key (seemingly not available in trial subscription)
#
class SysTran(BackendUtils):
    """ professional service, 50 languages """

    match = r"^systran"
    requires_key = True
    raises_error = True
    lang_detect = "auto"
    is_tested = 0.0  # no key

    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):
        resp = http.post(
            url=self.url,
            params={
                "input": text,
                "target": self.params["lang"],
                "source": self.params["from"],
                #"key": self.params["api_key"],
            },
            headers={
                "Authorization": "Key " + self.params["api_key"]
            }
        )
        data = resp.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"], resp.status_code, resp.headers)
        # nested result structure
        return data["outputs"][0]["output"]


# ArgosTranslate
#
#  · offline translation package (OpenNMT)







|








|
|
|
<



|
|



<






<
|
|







862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880

881
882
883
884
885
886
887
888

889
890
891
892
893
894

895
896
897
898
899
900
901
902
903
            if re.match(r"""^["']?[\{%$]""" + key + r"""[\}%$]?["']?$""", arg):
                return value
        return arg


# SYSTRAN Translate API
# · https://docs.systran.net/translateAPI/translation/
# · also requires an API key (trial subscriptions are working meanwhile)
#
class SysTran(BackendUtils):
    """ professional service, 50 languages """

    match = r"^systran"
    requires_key = True
    raises_error = True
    lang_detect = "auto"
    is_tested = 0.2  # actually tested now
    max_len = 10000
    api_url = "https://api-translate.systran.net/translation/text/translate"


    def fetch(self, text):
        resp = http.post(
            url=self.api_url,
            data={
                "input": text,
                "target": self.params["lang"],
                "source": self.params["from"],

            },
            headers={
                "Authorization": "Key " + self.params["api_key"]
            }
        )
        data = resp.json()   # if not JSON response, we probably ran into a HTTP/API error

        if resp.status_code != 200:
            raise Exception(data, resp.headers)
        # nested result structure
        return data["outputs"][0]["output"]


# ArgosTranslate
#
#  · offline translation package (OpenNMT)

Changes to tk_translate/__init__.py.

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
#!/usr/bin/env python3
# encoding: utf-8
# fmt: off
# api: pysimplegui
# type: gui
# title: standalone PageTranslate
# description: Utilizes translationbackends in trivial from→to texteditor
# category: transform
# version: 0.2
# state: beta
# license: MITL
# config: -
# priority: optional
# depends: python >= 3.8, python:PySimpleGUI >= 4.37, python:requests
# pack: pythonpath/*.py=gui/
# architecture: all
# classifiers: translation
# keywords: translation
# url: https://fossil.include-once.org/pagetranslate/
# doc-format: text/markdown
#
# **tk-translate** is a PySimpleGUI variant of
# [PageTranslate](https://fossil.include-once.org/pagetranslate/).
# It provides a terse GUI to get some text translated using one of the various
# services from PT or Deep-Translator. Albeit it has no config dialog, thus
# won't pacify API-key requirements. It's mostly just meant for testing.


#
# Presents two input boxes, some buttons, for plain text translations.
# Usage:
#
#  * Insert text into left input
#  * Select backend
#  * Change target language








|

















>
>







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
#!/usr/bin/env python3
# encoding: utf-8
# fmt: off
# api: pysimplegui
# type: gui
# title: standalone PageTranslate
# description: Utilizes translationbackends in trivial from→to texteditor
# category: transform
# version: 0.3
# state: beta
# license: MITL
# config: -
# priority: optional
# depends: python >= 3.8, python:PySimpleGUI >= 4.37, python:requests
# pack: pythonpath/*.py=gui/
# architecture: all
# classifiers: translation
# keywords: translation
# url: https://fossil.include-once.org/pagetranslate/
# doc-format: text/markdown
#
# **tk-translate** is a PySimpleGUI variant of
# [PageTranslate](https://fossil.include-once.org/pagetranslate/).
# It provides a terse GUI to get some text translated using one of the various
# services from PT or Deep-Translator. Albeit it has no config dialog, thus
# won't pacify API-key requirements. It's mostly just meant for testing.
#
# ![🗔](https://fossil.include-once.org/pagetranslate/raw/24ddd787008?m=image/png)
#
# Presents two input boxes, some buttons, for plain text translations.
# Usage:
#
#  * Insert text into left input
#  * Select backend
#  * Change target language
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
        "Linguee Dict",
        "PONS Dict",
        "dingonyms --merriam {text}",
        "LibreTranslate ⚿",
        "SysTRAN ⚿",
        "QCRI ⚿",
        "Yandex ⚿",
        "DeepL API ⚿",
        "DeepL Free ⚿",
        "DeepL Web ⛼",
        "Microsoft ⚿",
        "DeepTransApi: Google",
        "DeepTransApi: MyMemory",
        "deep_translator -trans 'google' -src 'auto' -tg {lang} -txt {text}",
        "argos-translate --from-lang {from} --to-lang {lang} {text}",
        "trans -sl {from} {text} {lang}",







<
|







107
108
109
110
111
112
113

114
115
116
117
118
119
120
121
        "Linguee Dict",
        "PONS Dict",
        "dingonyms --merriam {text}",
        "LibreTranslate ⚿",
        "SysTRAN ⚿",
        "QCRI ⚿",
        "Yandex ⚿",

        "DeepL Free/Pro API ⚿",
        "DeepL Web ⛼",
        "Microsoft ⚿",
        "DeepTransApi: Google",
        "DeepTransApi: MyMemory",
        "deep_translator -trans 'google' -src 'auto' -tg {lang} -txt {text}",
        "argos-translate --from-lang {from} --to-lang {lang} {text}",
        "trans -sl {from} {text} {lang}",