Add format base class, docstrings, and related changes
offlate/formats/androidstrings.py
1 | - | # Copyright (c) 2019 Julien Lepiller <julien@lepiller.eu> | |
1 | + | # Copyright (c) 2019, 2020 Julien Lepiller <julien@lepiller.eu> | |
2 | 2 | # | |
3 | 3 | # This program is free software: you can redistribute it and/or modify | |
4 | 4 | # it under the terms of the GNU Affero General Public License as | |
… | |||
20 | 20 | from pathlib import Path | |
21 | 21 | ||
22 | 22 | from .entry import AndroidStringsEntry | |
23 | + | from .format import Format | |
23 | 24 | ||
24 | - | class AndroidStringsFormat: | |
25 | + | class AndroidStringsFormat(Format): | |
25 | 26 | def __init__(self, conf): | |
26 | 27 | self.conf = conf | |
27 | 28 | self.translationfilename = conf["file"] | |
… | |||
59 | 60 | entry.dst = otranslated | |
60 | 61 | continue | |
61 | 62 | # otherwise, ntranslated and otranslated have a different value | |
62 | - | entry.dst = callback(envalue, otranslated, ntranslated) | |
63 | + | entry.dst = callback.mergeConflict(envalue, otranslated, ntranslated) | |
63 | 64 | self.translation.save() | |
64 | 65 | ||
65 | 66 | def getExternalFiles(self): |
offlate/formats/appstore.py
1 | - | # Copyright (c) 2019 Julien Lepiller <julien@lepiller.eu> | |
1 | + | # Copyright (c) 2019, 2020 Julien Lepiller <julien@lepiller.eu> | |
2 | 2 | # | |
3 | 3 | # This program is free software: you can redistribute it and/or modify | |
4 | 4 | # it under the terms of the GNU Affero General Public License as | |
… | |||
19 | 19 | from pathlib import Path | |
20 | 20 | ||
21 | 21 | from .entry import AppstoreEntry | |
22 | + | from .format import Format | |
22 | 23 | ||
23 | 24 | def findRecursive(directory): | |
24 | 25 | ans = [] | |
… | |||
30 | 31 | ans.extend(findRecursive(f)) | |
31 | 32 | return ans | |
32 | 33 | ||
33 | - | class AppstoreFormat: | |
34 | + | class AppstoreFormat(Format): | |
34 | 35 | def __init__(self, conf): | |
35 | 36 | self.conf = conf | |
36 | 37 | self.translationpath = conf["file"] | |
… | |||
64 | 65 | if ncontent == "": | |
65 | 66 | r.tr = oldr.tr | |
66 | 67 | continue | |
67 | - | r.tr = callback(r.en, ocontent, ncontent) | |
68 | + | r.tr = callback.mergeConflict(r.en, ocontent, ncontent) | |
68 | 69 | ||
69 | 70 | def getExternalFiles(self): | |
70 | 71 | return findRecursive(self.enpath).extend(findRecursive(self.translationpath)) |
offlate/formats/callback.py unknown status 1
1 | + | # Copyright (c) 2020 Julien Lepiller <julien@lepiller.eu> | |
2 | + | # | |
3 | + | # This program is free software: you can redistribute it and/or modify | |
4 | + | # it under the terms of the GNU Affero General Public License as | |
5 | + | # published by the Free Software Foundation, either version 3 of the | |
6 | + | # License, or (at your option) any later version. | |
7 | + | # | |
8 | + | # This program is distributed in the hope that it will be useful, | |
9 | + | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | + | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | + | # GNU Affero General Public License for more details. | |
12 | + | # | |
13 | + | # You should have received a copy of the GNU Affero General Public License | |
14 | + | # along with this program. If not, see <https://www.gnu.org/licenses/>. | |
15 | + | #### | |
16 | + | ||
17 | + | class FormatCallback: | |
18 | + | """ | |
19 | + | The base class for a proper FormatCallback class. | |
20 | + | """ | |
21 | + | def mergeConflict(self, base, oldTranslation, newTranslation): | |
22 | + | """ | |
23 | + | Resolve a merge conflict. This method is called when merging two | |
24 | + | Format instances. When a base string is translated in two different | |
25 | + | (non-empty) ways, a conflict arrises. | |
26 | + | ||
27 | + | :returns: The translation that will be used for this entry. | |
28 | + | :rtype: str | |
29 | + | """ | |
30 | + | raise Exception("Unimplemented method in concrete class: mergeConflict") |
offlate/formats/entry.py
1 | - | # Copyright (c) 2018, 2019 Julien Lepiller <julien@lepiller.eu> | |
1 | + | # Copyright (c) 2018, 2019, 2020 Julien Lepiller <julien@lepiller.eu> | |
2 | 2 | # | |
3 | 3 | # This program is free software: you can redistribute it and/or modify | |
4 | 4 | # it under the terms of the GNU Affero General Public License as | |
… | |||
15 | 15 | #### | |
16 | 16 | ||
17 | 17 | class Entry: | |
18 | + | """ | |
19 | + | Set of very related pairs of base language strings and their translations. | |
20 | + | Most of the time, and entry contains only one base language string and one | |
21 | + | translation. Some formats handle plurals, and each variant is saved as an | |
22 | + | additional string in the same entry. | |
23 | + | """ | |
18 | 24 | def __init__(self, msgids, msgstrs, fuzzy, obsolete): | |
25 | + | """ | |
26 | + | Create an entry. | |
27 | + | ||
28 | + | :param strs msgids: the set of base language strings | |
29 | + | :param strs msgstrs: the set of target language strings (translated) | |
30 | + | :param bool fuzzy: whether the entry is fuzzy | |
31 | + | :param bool obsolete: whether the entry is obsolete | |
32 | + | """ | |
19 | 33 | self.msgids = msgids | |
20 | 34 | self.msgstrs = msgstrs | |
21 | 35 | self.fuzzy = fuzzy | |
22 | 36 | self.obsolete = obsolete | |
23 | 37 | ||
24 | 38 | def isTranslated(self): | |
39 | + | """ | |
40 | + | :returns: Whether the entry is fully translated | |
41 | + | :rtype: bool | |
42 | + | """ | |
25 | 43 | for msgstr in self.msgstrs: | |
26 | 44 | if msgstr == '': | |
27 | 45 | return False | |
28 | 46 | return True | |
29 | 47 | ||
30 | 48 | def isFuzzy(self): | |
49 | + | """ | |
50 | + | :returns: Whether the entry is fuzzy | |
51 | + | :rtype: bool | |
52 | + | """ | |
31 | 53 | return self.fuzzy | |
32 | 54 | ||
33 | 55 | def isObsolete(self): | |
56 | + | """ | |
57 | + | :returns: Whether the entry is obsolete | |
58 | + | :rtype: bool | |
59 | + | """ | |
34 | 60 | return self.obsolete | |
35 | 61 | ||
36 | 62 | def update(self, index, content): | |
63 | + | """ | |
64 | + | Update the entry by replacing the translation number index with content. | |
65 | + | ||
66 | + | :rtype: None | |
67 | + | """ | |
37 | 68 | self.msgstrs[index] = content | |
38 | 69 | ||
39 | 70 | def get(self, index): | |
71 | + | """ | |
72 | + | :returns: The indexth translation in this entry. | |
73 | + | :rtype: str | |
74 | + | """ | |
40 | 75 | if isinstance(self.msgstrs, list): | |
41 | 76 | return self.msgstrs[index] | |
42 | 77 | else: | |
43 | 78 | return list(self.msgstrs.items())[index][1] | |
44 | 79 | ||
45 | 80 | def isPlural(self): | |
81 | + | """ | |
82 | + | :retuns: Whether the entry is an entry for a plural form. | |
83 | + | :rtype: bool | |
84 | + | """ | |
46 | 85 | return len(self.msgstrs) > 1 | |
47 | 86 | ||
48 | 87 | class POEntry(Entry): | |
88 | + | """ | |
89 | + | An entry for the gettext format. | |
90 | + | """ | |
49 | 91 | def __init__(self, entry): | |
50 | 92 | msgids = [entry.msgid] | |
51 | 93 | msgstrs = [entry.msgstr] | |
… | |||
67 | 109 | self.entry.msgstr = content | |
68 | 110 | ||
69 | 111 | class AndroidStringsEntry(Entry): | |
112 | + | """ | |
113 | + | An entry for the android translation format. | |
114 | + | """ | |
70 | 115 | def __init__(self, entry, parent=None, index=None): | |
71 | 116 | if entry.type == 'string': | |
72 | 117 | msgids = [entry.orig] | |
… | |||
93 | 138 | return isinstance(self.msgstrs, dict) | |
94 | 139 | ||
95 | 140 | class AppstoreEntry(Entry): | |
141 | + | """ | |
142 | + | An entry for the android app description format. | |
143 | + | """ | |
96 | 144 | def __init__(self, filename, en, tr): | |
97 | 145 | Entry.__init__(self, [en], [tr], False, False) | |
98 | 146 | self.en = en | |
… | |||
105 | 153 | ||
106 | 154 | ||
107 | 155 | class JSONEntry(Entry): | |
156 | + | """ | |
157 | + | An entry for the json format. | |
158 | + | """ | |
108 | 159 | def __init__(self, entry): | |
109 | 160 | Entry.__init__(self, [entry['source_string']], [entry['translation']], False, False) | |
110 | 161 | self.entry = entry | |
… | |||
114 | 165 | self.entry['translation'] = content | |
115 | 166 | ||
116 | 167 | class YAMLEntry(Entry): | |
168 | + | """ | |
169 | + | An entry for the yaml format. | |
170 | + | """ | |
117 | 171 | def __init__(self, entry): | |
118 | 172 | self.entry = entry | |
119 | 173 | Entry.__init__(self, [entry['source_string']], |
offlate/formats/exception.py unknown status 1
1 | + | # Copyright (c) 2018 Julien Lepiller <julien@lepiller.eu> | |
2 | + | # | |
3 | + | # This program is free software: you can redistribute it and/or modify | |
4 | + | # it under the terms of the GNU Affero General Public License as | |
5 | + | # published by the Free Software Foundation, either version 3 of the | |
6 | + | # License, or (at your option) any later version. | |
7 | + | # | |
8 | + | # This program is distributed in the hope that it will be useful, | |
9 | + | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | + | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | + | # GNU Affero General Public License for more details. | |
12 | + | # | |
13 | + | # You should have received a copy of the GNU Affero General Public License | |
14 | + | # along with this program. If not, see <https://www.gnu.org/licenses/>. | |
15 | + | #### | |
16 | + | ||
17 | + | class UnsupportedFormatException(Exception): | |
18 | + | def __init__(self, message): | |
19 | + | super().__init__('Unsupported format: '+message) | |
20 | + | self.unsupportedFormat = message |
offlate/formats/format.py unknown status 1
1 | + | # Copyright (c) 2020 Julien Lepiller <julien@lepiller.eu> | |
2 | + | # | |
3 | + | # This program is free software: you can redistribute it and/or modify | |
4 | + | # it under the terms of the GNU Affero General Public License as | |
5 | + | # published by the Free Software Foundation, either version 3 of the | |
6 | + | # License, or (at your option) any later version. | |
7 | + | # | |
8 | + | # This program is distributed in the hope that it will be useful, | |
9 | + | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | + | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | + | # GNU Affero General Public License for more details. | |
12 | + | # | |
13 | + | # You should have received a copy of the GNU Affero General Public License | |
14 | + | # along with this program. If not, see <https://www.gnu.org/licenses/>. | |
15 | + | #### | |
16 | + | ||
17 | + | class Format: | |
18 | + | """ | |
19 | + | The base class for translation formats. | |
20 | + | ||
21 | + | A format is a class that is linked to a project. Each instance of this | |
22 | + | class represents the files that contain the source and target strings | |
23 | + | in a specific format (gettext, yaml, json, ...). | |
24 | + | """ | |
25 | + | ||
26 | + | def __init__(self, conf): | |
27 | + | self.conf = conf | |
28 | + | ||
29 | + | def content(self): | |
30 | + | """ | |
31 | + | Return the content of this set of source and target strings. | |
32 | + | ||
33 | + | You may use these as read-write object. Any change to the content | |
34 | + | can later be saved using the save method. | |
35 | + | ||
36 | + | :returns: List of entries | |
37 | + | :rtype: Entry list | |
38 | + | """ | |
39 | + | raise Exception("Unimplemented method in concrete class: content") | |
40 | + | ||
41 | + | def save(self): | |
42 | + | """ | |
43 | + | Saves the content to files. | |
44 | + | ||
45 | + | This method assumes that the content (Entry objects) is modified | |
46 | + | in-place, and will save their content. | |
47 | + | ||
48 | + | :rtype: None | |
49 | + | """ | |
50 | + | raise Exception("Unimplemented method in concrete class: save") | |
51 | + | ||
52 | + | def merge(self, older, callback): | |
53 | + | """ | |
54 | + | Merge two versions of the same translation into this translation set | |
55 | + | and save the result. | |
56 | + | ||
57 | + | :param Format older: The previous version of the translation | |
58 | + | :param FormatCallback callback: A format callback to help deal with | |
59 | + | merge conflicts. | |
60 | + | :rtype: None | |
61 | + | """ | |
62 | + | raise Exception("Unimplemented method in concrete class: merge") | |
63 | + | ||
64 | + | def reload(self): | |
65 | + | """ | |
66 | + | Reloads the content from the files without saving existing changes. | |
67 | + | ||
68 | + | :rtype: None | |
69 | + | """ | |
70 | + | raise Exception("Unimplemented method in concrete class: reload") | |
71 | + | ||
72 | + | def getExternalFiles(self): | |
73 | + | """ | |
74 | + | :returns: The list of files this instance may read or write | |
75 | + | :rtype: String list | |
76 | + | """ | |
77 | + | raise Exception("Unimplemented method in concrete class: getExternalFiles") | |
78 | + | ||
79 | + | def getTranslationFiles(self): | |
80 | + | """ | |
81 | + | A format can be composed of different files. For instance, a file | |
82 | + | that contains the English (or base) language version of the strings, | |
83 | + | and a file that contains the translated strings. | |
84 | + | ||
85 | + | :returns: The list of files that contain the actual translation | |
86 | + | :rtype: String list | |
87 | + | """ | |
88 | + | raise Exception("Unimplemented method in concrete class: getTranslationFiles") |
offlate/formats/formatException.py unknown status 2
1 | - | # Copyright (c) 2018 Julien Lepiller <julien@lepiller.eu> | |
2 | - | # | |
3 | - | # This program is free software: you can redistribute it and/or modify | |
4 | - | # it under the terms of the GNU Affero General Public License as | |
5 | - | # published by the Free Software Foundation, either version 3 of the | |
6 | - | # License, or (at your option) any later version. | |
7 | - | # | |
8 | - | # This program is distributed in the hope that it will be useful, | |
9 | - | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | - | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | - | # GNU Affero General Public License for more details. | |
12 | - | # | |
13 | - | # You should have received a copy of the GNU Affero General Public License | |
14 | - | # along with this program. If not, see <https://www.gnu.org/licenses/>. | |
15 | - | #### | |
16 | - | ||
17 | - | class UnsupportedFormatException(Exception): | |
18 | - | def __init__(self, message): | |
19 | - | super().__init__('Unsupported format: '+message) | |
20 | - | self.unsupportedFormat = message |
offlate/formats/gettext.py
1 | - | # Copyright (c) 2018 Julien Lepiller <julien@lepiller.eu> | |
1 | + | # Copyright (c) 2018, 2020 Julien Lepiller <julien@lepiller.eu> | |
2 | 2 | # | |
3 | 3 | # This program is free software: you can redistribute it and/or modify | |
4 | 4 | # it under the terms of the GNU Affero General Public License as | |
… | |||
20 | 20 | import os.path | |
21 | 21 | from dateutil.tz import tzlocal | |
22 | 22 | from .entry import POEntry | |
23 | + | from .format import Format | |
23 | 24 | ||
24 | - | class GettextFormat: | |
25 | + | class GettextFormat(Format): | |
25 | 26 | def __init__(self, conf): | |
26 | 27 | self.pofilename = conf["file"] | |
27 | 28 | self.pot = polib.pofile(conf["pot"]) | |
… | |||
54 | 55 | nentry.msgstr = oentry.msgstr | |
55 | 56 | break | |
56 | 57 | # otherwise, nentry and oentry have a different msgstr | |
57 | - | nentry.msgstr = callback(nentry.msgid, oentry.msgstr, nentry.msgstr) | |
58 | + | nentry.msgstr = callback.mergeConflict(nentry.msgid, | |
59 | + | oentry.msgstr, nentry.msgstr) | |
58 | 60 | break | |
59 | 61 | self.po.save() | |
60 | 62 |
offlate/formats/list.py unknown status 2
1 | - | # Copyright (c) 2018 Julien Lepiller <julien@lepiller.eu> | |
2 | - | # | |
3 | - | # This program is free software: you can redistribute it and/or modify | |
4 | - | # it under the terms of the GNU Affero General Public License as | |
5 | - | # published by the Free Software Foundation, either version 3 of the | |
6 | - | # License, or (at your option) any later version. | |
7 | - | # | |
8 | - | # This program is distributed in the hope that it will be useful, | |
9 | - | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | - | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | - | # GNU Affero General Public License for more details. | |
12 | - | # | |
13 | - | # You should have received a copy of the GNU Affero General Public License | |
14 | - | # along with this program. If not, see <https://www.gnu.org/licenses/>. | |
15 | - | #### | |
16 | - | """ The list of available formats. """ | |
17 | - | ||
18 | - | GETTEXT = 0 | |
19 | - | YAML = 1 | |
20 | - | ||
21 | - | format_list = [ | |
22 | - | "Gettext (.po)", | |
23 | - | "YAML (.yml)", | |
24 | - | "Qt (.ts)", | |
25 | - | "Android (strings.xml)" | |
26 | - | ] |
offlate/formats/ts.py
1 | - | # Copyright (c) 2018 Julien Lepiller <julien@lepiller.eu> | |
1 | + | # Copyright (c) 2018, 2020 Julien Lepiller <julien@lepiller.eu> | |
2 | 2 | # | |
3 | 3 | # This program is free software: you can redistribute it and/or modify | |
4 | 4 | # it under the terms of the GNU Affero General Public License as | |
… | |||
20 | 20 | import xml.etree.ElementTree as ET | |
21 | 21 | import time | |
22 | 22 | from .entry import TSEntry | |
23 | + | from .format import Format | |
23 | 24 | ||
24 | 25 | from PyQt5.QtCore import * | |
25 | 26 | ||
… | |||
194 | 195 | return nplural | |
195 | 196 | return 0 | |
196 | 197 | ||
197 | - | class TSFormat: | |
198 | + | class TSFormat(Format): | |
198 | 199 | def __init__(self, conf): | |
199 | 200 | self.conf = conf | |
200 | 201 | self.tsfilename = conf["file"] | |
201 | 202 | if not os.path.isfile(conf["file"]): | |
202 | - | self.createNewTS() | |
203 | + | self._createNewTS() | |
203 | 204 | self.reload() | |
204 | 205 | ||
205 | - | def createNewTS(self): | |
206 | + | def _createNewTS(self): | |
206 | 207 | template = ET.parse(self.conf["template"]) | |
207 | 208 | content = template.getroot() | |
208 | 209 | root = ET.Element('TS') | |
… | |||
300 | 301 | elif oentry.msgstrs[i] == '': | |
301 | 302 | break | |
302 | 303 | else: | |
303 | - | entry.update(i, callback(entry.msgids[0], | |
304 | + | entry.update(i, callback.mergeConflict(entry.msgids[0], | |
304 | 305 | oentry.msgstrs[i], entry.msgstrs[i])) | |
305 | 306 | break | |
306 | 307 | self.save() |
offlate/formats/yaml.py
1 | - | # Copyright (c) 2018 Julien Lepiller <julien@lepiller.eu> | |
1 | + | # Copyright (c) 2018, 2020 Julien Lepiller <julien@lepiller.eu> | |
2 | 2 | # | |
3 | 3 | # This program is free software: you can redistribute it and/or modify | |
4 | 4 | # it under the terms of the GNU Affero General Public License as | |
… | |||
17 | 17 | ||
18 | 18 | from ruamel import yaml | |
19 | 19 | from .entry import YAMLEntry | |
20 | + | from .format import Format | |
20 | 21 | ||
21 | 22 | def yaml_rec_load(path, source, dest): | |
22 | 23 | ans = [] | |
… | |||
51 | 52 | elif n == '': | |
52 | 53 | ans[i] = o | |
53 | 54 | else: | |
54 | - | ans[i] = callback(s, o, n) | |
55 | + | ans[i] = callback.mergeConflict(s, o, n) | |
55 | 56 | else: | |
56 | 57 | ans[i] = yaml_rec_update(callback, s, o, n) | |
57 | 58 | return ans | |
58 | 59 | ||
59 | - | class YamlFormat: | |
60 | + | class YamlFormat(Format): | |
60 | 61 | def __init__(self, conf): | |
61 | 62 | self.conf = conf | |
62 | 63 | self.source = conf['source'] |
offlate/manager.py
19 | 19 | from .systems.transifex import TransifexProject | |
20 | 20 | from .systems.gitlab import GitlabProject | |
21 | 21 | from .systems.github import GithubProject | |
22 | - | from .formats.formatException import UnsupportedFormatException | |
22 | + | from .formats.exception import UnsupportedFormatException | |
23 | 23 | from .systems.list import * | |
24 | 24 | ||
25 | 25 | import json |
offlate/systems/git.py
25 | 25 | from ..formats.yaml import YamlFormat | |
26 | 26 | from ..formats.androidstrings import AndroidStringsFormat | |
27 | 27 | from ..formats.appstore import AppstoreFormat | |
28 | - | from ..formats.formatException import UnsupportedFormatException | |
28 | + | from ..formats.exception import UnsupportedFormatException | |
29 | 29 | from .systemException import ProjectNotFoundSystemException | |
30 | 30 | ||
31 | 31 | def rmdir(dir): |
offlate/systems/transifex.py
20 | 20 | import requests | |
21 | 21 | from requests.auth import HTTPBasicAuth | |
22 | 22 | from ..formats.yaml import YamlFormat | |
23 | - | from ..formats.formatException import UnsupportedFormatException | |
23 | + | from ..formats.exception import UnsupportedFormatException | |
24 | 24 | from .systemException import ProjectNotFoundSystemException | |
25 | 25 | ||
26 | 26 | class TransifexProject: |
offlate/ui/editor.py
21 | 21 | from .spellcheckedit import SpellCheckEdit | |
22 | 22 | from .tagclickedit import TagClickEdit | |
23 | 23 | from .parallel import RunnableCallback, RunnableSignals | |
24 | + | from ..formats.callback import FormatCallback | |
24 | 25 | ||
25 | 26 | import math | |
26 | 27 | import platform | |
… | |||
35 | 36 | def __init__(self, parent = None): | |
36 | 37 | super(ProjectTab, self).__init__(parent) | |
37 | 38 | ||
39 | + | class MergeCallback(FormatCallback): | |
40 | + | def __init__(self): | |
41 | + | pass | |
42 | + | ||
43 | + | def mergeConflict(self, base, oldTranslation, newTranslation): | |
44 | + | # TODO: Actually do something more intelligent | |
45 | + | return newTranslation | |
46 | + | ||
38 | 47 | class Interface: | |
39 | 48 | def __init__(self): | |
40 | 49 | self.value = None | |
… | |||
366 | 375 | self.project.save() | |
367 | 376 | self.project.send(Interface()) | |
368 | 377 | ||
369 | - | def askmerge(self, msgid, oldstr, newstr): | |
370 | - | # TODO: Actually do something more intelligent | |
371 | - | return newstr | |
372 | - | ||
373 | 378 | def update(self, callback=None): | |
374 | 379 | self.project.save() | |
375 | - | self.project.update(self.askmerge, callback) | |
380 | + | self.project.update(MergeCallback(), callback) | |
376 | 381 | self.content = self.project.content() | |
377 | 382 | self.updateContent() | |
378 | 383 |
offlate/ui/manager.py
30 | 30 | ||
31 | 31 | from ..manager import ProjectManager | |
32 | 32 | ||
33 | - | from ..formats.formatException import UnsupportedFormatException | |
33 | + | from ..formats.exception import UnsupportedFormatException | |
34 | 34 | from ..systems.systemException import ProjectNotFoundSystemException | |
35 | 35 | ||
36 | 36 | class ProjectManagerWindow(QMainWindow): |
offlate/ui/new.py
24 | 24 | from PyQt5.QtCore import * | |
25 | 25 | ||
26 | 26 | from ..systems.list import * | |
27 | - | from ..formats.list import * | |
28 | 27 | from .multiplelineedit import MultipleLineEdit | |
29 | 28 | ||
30 | 29 | class NewWindow(QDialog): |
offlate/ui/parallel.py
15 | 15 | #### | |
16 | 16 | ||
17 | 17 | from PyQt5.QtCore import * | |
18 | - | from ..formats.formatException import UnsupportedFormatException | |
18 | + | from ..formats.exception import UnsupportedFormatException | |
19 | 19 | from ..systems.systemException import ProjectNotFoundSystemException | |
20 | 20 | ||
21 | 21 | class RunnableCallback: |