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

⌈⌋ branch:  PageTranslate


Check-in [314a6459fe]

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

Overview
Comment:move linebreak/quick into options, condense pluginconf.gui.window() invocation
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 314a6459fe936b0c76504c6c2d24fcec35fa7052
User & Date: mario 2022-10-24 20:39:48
Context
2022-10-25
09:35
introduce .xml() translation mode (just DeepL really, and only used in tk_translate file mode) check-in: 39daeee3a0 user: mario tags: trunk
2022-10-24
20:39
move linebreak/quick into options, condense pluginconf.gui.window() invocation check-in: 314a6459fe user: mario tags: trunk
07:58
add settings dialog after all, move xml translation to BackendUtils check-in: 93f12505a9 user: mario tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to test/assign.py.

31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
    for name in "DT: Google", "Pons Dict", "DT: Libre":
        assert isinstance(_ab(name), tb.DeepTranslator)
    assert isinstance(_ab("DTA: Any"), tb.DeepTransApi)

def classes():
    assert len(tb.BackendUtils.subclasses()) >= 10

def dec_silcence():
    func = tb.BackendUtils.silence(lambda x: undef)
    func(2)

def dec_from_words():
    ls = []
    func = tb.BackendUtils.from_words(lambda text: ls.append(text))
    func("one two three")
    assert len(ls) == 3








|









31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
    for name in "DT: Google", "Pons Dict", "DT: Libre":
        assert isinstance(_ab(name), tb.DeepTranslator)
    assert isinstance(_ab("DTA: Any"), tb.DeepTransApi)

def classes():
    assert len(tb.BackendUtils.subclasses()) >= 10

def dec_silence():
    func = tb.BackendUtils.silence(lambda x: undef)
    func(2)

def dec_from_words():
    ls = []
    func = tb.BackendUtils.from_words(lambda text: ls.append(text))
    func("one two three")
    assert len(ls) == 3

Added test/pt_country.py.































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import sys
sys.path.append(".")
import pagetranslate as pt

class StubPt(pt.PageTranslate):
    def __init__(self, *x):
        pass

def country():
    p = StubPt()
    assert p.country("it") == "IT"
    assert p.country("en") == "US"
    assert p.country("ilo") == "PH"


Changes to tk_translate/__init__.py.

8
9
10
11
12
13
14


15
16
17
18
19
20
21
# category: transform
# version: 0.5
# state: beta
# license: MITL
# config:
#    { name: source, value: auto, description: assumed source language }
#    { name: target, value: en, description: default target language }


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







>
>







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# category: transform
# version: 0.5
# state: beta
# license: MITL
# config:
#    { name: source, value: auto, description: assumed source language }
#    { name: target, value: en, description: default target language }
#    { name: linebreakwise, type: bool, value: 0, description: linebreakwise paragraph translation }
#    { name: quick, type: bool, value: 0, description: quick placeholder linebreaks in requests }
# 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/
91
92
93
94
95
96
97

98
99
100
101
102
103
104
#sys.excepthook = lambda *exc: log.critical(format_exc())


#-- init
conf = dict(
    mode = "page",      # unused
    quick = 0,          # split/iterate over text sections

    api_key = "",       # API key
    email = "",         # MyMemory email
    cmd = "translate-cli -o -f auto -t {lang} {text}",  # if cli tool
    backend = "GoogleWeb",
    available = [
        "Google Translate",
        "Google Ajax",







>







93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#sys.excepthook = lambda *exc: log.critical(format_exc())


#-- init
conf = dict(
    mode = "page",      # unused
    quick = 0,          # split/iterate over text sections
    linebreakwise = 0,  # individual API requests for paragraphs (not sure we still need it)
    api_key = "",       # API key
    email = "",         # MyMemory email
    cmd = "translate-cli -o -f auto -t {lang} {text}",  # if cli tool
    backend = "GoogleWeb",
    available = [
        "Google Translate",
        "Google Ajax",
137
138
139
140
141
142
143

144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
        "hu", "is", "ga", "la", "lv", "lt", "lb", "mk", "mt", "ro", "sr", "sk",
        "sl", "uk", "wl", "cy", "yi", "ms", "ha", "ho", "mi", "mh", "tl", "tp",
        "pi", "po", "sm", "lo", "pa", "en", "se", "en", "fl", "en",
    ],
    office = f"TkInter/{tk.TkVersion}",
)
# load config

conf.update(
    sg.UserSettings(filename="tk_translate.json").load().items()
)
# argument
if len(sys.argv) == 2:
    conf["backend"] = sys.argv[1]


#-- widget structure
layout = [[
    sg.Column([
        # top frame
        [
            sg.Combo(values=conf["available"], default_value=conf["backend"], size=(20,25), key="backend", tooltip="Service to use", background_color="#67a"),
            sg.Sizer(h_pixels = 140),
            sg.Combo(values=["auto"]+conf["languages"], default_value=conf["source"], size=(4,1), key="from", tooltip="Source language", background_color="#67a"),
            sg.Sizer(h_pixels = 30),
            sg.Button("➜ Translate ➜", tooltip="Translate to target language"),
            sg.Sizer(h_pixels = 30),
            sg.Combo(values=conf["languages"], default_value=conf["target"], size=(4,30), key="lang", tooltip="Target language", background_color="#67a"),
            sg.Sizer(h_pixels = 160),
            sg.Checkbox("␤␍⮒", key="linebreakwise", default=False, tooltip="Use .linebreakwise() translation (in case all text gets contracted)"),
            sg.Button("⛭", key="settings", tooltip="Config"),
            sg.Button("File", key="file", tooltip="Translate an OpenOffice or text file"),
        ],
        # tabs
        [
            sg.Multiline(size=(55,20), key="orig", background_color="#cce"),
            sg.Multiline(size=(55,20), key="outp", background_color="#cce"),







>
|
<
<

















|
|







140
141
142
143
144
145
146
147
148


149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
        "hu", "is", "ga", "la", "lv", "lt", "lb", "mk", "mt", "ro", "sr", "sk",
        "sl", "uk", "wl", "cy", "yi", "ms", "ha", "ho", "mi", "mh", "tl", "tp",
        "pi", "po", "sm", "lo", "pa", "en", "se", "en", "fl", "en",
    ],
    office = f"TkInter/{tk.TkVersion}",
)
# load config
sg.user_settings_filename(filename="tk_translate.json")
conf.update(sg.user_settings())


# argument
if len(sys.argv) == 2:
    conf["backend"] = sys.argv[1]


#-- widget structure
layout = [[
    sg.Column([
        # top frame
        [
            sg.Combo(values=conf["available"], default_value=conf["backend"], size=(20,25), key="backend", tooltip="Service to use", background_color="#67a"),
            sg.Sizer(h_pixels = 140),
            sg.Combo(values=["auto"]+conf["languages"], default_value=conf["source"], size=(4,1), key="from", tooltip="Source language", background_color="#67a"),
            sg.Sizer(h_pixels = 30),
            sg.Button("➜ Translate ➜", tooltip="Translate to target language"),
            sg.Sizer(h_pixels = 30),
            sg.Combo(values=conf["languages"], default_value=conf["target"], size=(4,30), key="lang", tooltip="Target language", background_color="#67a"),
            sg.Sizer(h_pixels = 225),
            #sg.Checkbox("␤␍⮒", key="linebreakwise", default=False, tooltip="Use .linebreakwise() translation (in case all text gets contracted)"),
            sg.Button("⛭", key="settings", tooltip="Config"),
            sg.Button("File", key="file", tooltip="Translate an OpenOffice or text file"),
        ],
        # tabs
        [
            sg.Multiline(size=(55,20), key="orig", background_color="#cce"),
            sg.Multiline(size=(55,20), key="outp", background_color="#cce"),
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
        data.update(conf)
        if re.search(r"\{(|text|lang|from)\}|\s(-w\b|--\w\w+)", data["backend"]):
            data.update({
                "cmd": data["backend"],
                "backend": "CLI",
            })
        t = translationbackends.assign_service(data)
        translate = t.linebreakwise if data.get("linebreakwise") else t.translate
        self.w["outp"].update(translate(data["orig"]))

    # File translation (odt, fodt)
    def file(self, data):
        fn = sg.tk.filedialog.askopenfilename(
            filetypes=[("Office", "*.odt *.fodt *.odg *.fodg *.odp *.fodp"), ("Text", "*.txt *.md"), ("Any", "*")]
        )
        if not fn:
            return

        target_fn = re.sub(r"(\.\w+)$", rf".{data['lang']}\1", fn)
        if os.path.exists(target_fn):
            if sg.popup_yes_no(f"Overwrite {target_fn}?") != "Yes":
                return

        self.t = translationbackends.assign_service(data)
        if re.search("\.(txt|md|text)$", fn):
            self.file_text(fn, target_fn)
        if re.search("\.(fodt|fodg|fodp)$", fn):
            self.file_xml(fn, target_fn)
        if re.search("\.(odt|odg|odp)$", fn):
            self.file_zip(fn, target_fn)








|















|







283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
        data.update(conf)
        if re.search(r"\{(|text|lang|from)\}|\s(-w\b|--\w\w+)", data["backend"]):
            data.update({
                "cmd": data["backend"],
                "backend": "CLI",
            })
        t = translationbackends.assign_service(data)
        translate = t.linebreakwise if conf.get("linebreakwise") else t.translate
        self.w["outp"].update(translate(data["orig"]))

    # File translation (odt, fodt)
    def file(self, data):
        fn = sg.tk.filedialog.askopenfilename(
            filetypes=[("Office", "*.odt *.fodt *.odg *.fodg *.odp *.fodp"), ("Text", "*.txt *.md"), ("Any", "*")]
        )
        if not fn:
            return

        target_fn = re.sub(r"(\.\w+)$", rf".{data['lang']}\1", fn)
        if os.path.exists(target_fn):
            if sg.popup_yes_no(f"Overwrite {target_fn}?") != "Yes":
                return

        self.t = translationbackends.assign_service(conf)
        if re.search("\.(txt|md|text)$", fn):
            self.file_text(fn, target_fn)
        if re.search("\.(fodt|fodg|fodp)$", fn):
            self.file_xml(fn, target_fn)
        if re.search("\.(odt|odg|odp)$", fn):
            self.file_zip(fn, target_fn)

325
326
327
328
329
330
331
332
333
334
335

336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351

352
353
354
355
356
357
358
359
360
361
362
                self.t.xml(
                    read.read()
                )
            )

    # modify content.xml within zip
    def file_zip(self, fn, target_fn):
        import zipfile
        shutil.copy(fn, target_fn)
        with zipfile.ZipFile(target_fn, 'a') as zip:
            zip.writestr(

                "content.xml",   # retains the original content.xml, but LO loads the appended update
                self.t.xml(
                    zip.read("content.xml").decode("utf-8")
                )
            )

    # File: Settings - remapped to pluginconf window
    def settings(self, data):
        import pluginconf.gui
        files = [__file__, translationbackends.__file__]
        conf_dict = {
            key: conf[key] for key in ["backend", "api_key", "email", "quick", "cmd", "source", "target"]
        }
        save = pluginconf.gui.window(conf_dict, {}, files=files, theme="Default1")
        if save:
            conf.update(conf_dict)

            us = sg.UserSettings(filename="tk_translate.json")
            for k,v in conf_dict.items():
                us.set(k, v)
            us.save(us.get_filename())

    # File: Exit
    def exit(self, data):
        self.w.close()

    # set mouse pointer ("watch" for planned hangups)
    def _cursor(self, s="arrow"):







|
<
|
|
>
|
|
|
|
<
<

|

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







327
328
329
330
331
332
333
334

335
336
337
338
339
340
341


342
343
344




345


346
347



348
349
350
351
352
353
354
                self.t.xml(
                    read.read()
                )
            )

    # modify content.xml within zip
    def file_zip(self, fn, target_fn):
        from zipfile import ZipFile, ZIP_DEFLATED

        with ZipFile(fn, 'r') as read, ZipFile(target_fn, 'w', compression=ZIP_DEFLATED) as write:
            for entry in read.infolist():
                data = read.read(entry.filename)
                if entry.filename == "content.xml":
                    data = self.t.xml(data.decode("utf-8"))
                write.writestr(entry.filename, data)



    # File: Settings - remapped to pluginconf window
    def settings(self, data, files=[__file__, translationbackends.__file__]):
        import pluginconf.gui




        if pluginconf.gui.window(conf, {}, files=files, theme="Default1"):


            for key in "backend", "api_key", "email", "cmd", "source", "target", "quick", "linebreakwise":
                sg.user_settings_set_entry(key, conf[key])  # don't fixate other presets




    # File: Exit
    def exit(self, data):
        self.w.close()

    # set mouse pointer ("watch" for planned hangups)
    def _cursor(self, s="arrow"):