Improve format support
CONTRIBUTING.md
91 | 91 | ``` | |
92 | 92 | ||
93 | 93 | Saves the list of Entry. | |
94 | + | ||
95 | + | ```python | |
96 | + | merge(oldformat, callback) | |
97 | + | ``` | |
98 | + | ||
99 | + | Merge the file in the old file. The callback function is the one passed to | |
100 | + | `System.update`. |
offlate/formats/gettext.py
6 | 6 | from .entry import POEntry | |
7 | 7 | ||
8 | 8 | class GettextFormat: | |
9 | - | def __init__(self, files, conf): | |
10 | - | self.po = polib.pofile(files["file"]) | |
9 | + | def __init__(self, conf): | |
10 | + | self.po = polib.pofile(conf["file"]) | |
11 | + | self.pot = polib.pofile(conf["pot"]) | |
11 | 12 | self.conf = conf | |
12 | 13 | ||
13 | 14 | def content(self): | |
… | |||
20 | 21 | self.po.metadata['Language'] = self.conf['lang'] | |
21 | 22 | self.po.metadata['X-Generator'] = 'Offlate ' + self.conf['version'] | |
22 | 23 | self.po.save() | |
24 | + | ||
25 | + | def merge(self, older, callback): | |
26 | + | older.po.merge(self.pot) | |
27 | + | self.po.save() | |
28 | + | for oentry in older.po: | |
29 | + | for nentry in self.po: | |
30 | + | if oentry.msgid == nentry.msgid: | |
31 | + | if oentry.msgstr == nentry.msgstr: | |
32 | + | break | |
33 | + | if oentry.msgstr == "": | |
34 | + | break | |
35 | + | if nentry.msgstr == "": | |
36 | + | nentry.msgstr = oentry.msgstr | |
37 | + | break | |
38 | + | # otherwise, nentry and oentry have a different msgstr | |
39 | + | nentry.msgstr = callback(nentry.msgid, oentry.msgstr, nentry.msgstr) | |
40 | + | break | |
41 | + | self.po.save() | |
42 | + |
offlate/systems/tp.py
77 | 77 | self.updateVersion() | |
78 | 78 | self.updateFileName() | |
79 | 79 | self.updateGettextNames() | |
80 | + | newname = self.popath | |
81 | + | self.popath = self.popath + '.new.po' | |
80 | 82 | self.getpot() | |
81 | - | po = polib.pofile(oldpath) | |
82 | - | po.merge(polib.pofile(self.potpath)) | |
83 | - | po.save() | |
84 | - | if oldname == self.filename: | |
85 | - | self.popath = self.popath + '.new.po' | |
86 | - | self.getpo() | |
87 | - | self.merge(self.popath, oldpath, callback) | |
88 | - | os.remove(oldpath) | |
89 | - | os.rename(self.popath, oldpath) | |
90 | - | self.popath = oldpath | |
91 | - | else: | |
92 | - | self.getpo() | |
93 | - | self.merge(self.popath, oldpath, callback) | |
94 | - | os.remove(oldpath) | |
95 | - | ||
96 | - | def merge(self, tofile, fromfile, callback): | |
97 | - | newpo = polib.pofile(tofile) | |
98 | - | oldpo = polib.pofile(fromfile) | |
99 | - | # If msgid is not found in to file, it's an old one, so ignore. | |
100 | - | # Otherwise, attempt to merge. | |
101 | - | for oentry in oldpo: | |
102 | - | for nentry in newpo: | |
103 | - | if oentry.msgid == nentry.msgid: | |
104 | - | if oentry.msgstr == nentry.msgstr: | |
105 | - | break | |
106 | - | if oentry.msgstr == "": | |
107 | - | break | |
108 | - | if nentry.msgstr == "": | |
109 | - | nentry.msgstr = oentry.msgstr | |
110 | - | break | |
111 | - | # else, nentry and oentry have a different msgstr | |
112 | - | nentry.msgstr = callback(nentry.msgid, oentry.msgstr, nentry.msgstr) | |
113 | - | break | |
114 | - | newpo.save() | |
83 | + | self.getpo() | |
84 | + | newcontent = GettextFormat( | |
85 | + | {'file': self.popath, | |
86 | + | 'pot': self.potpath, | |
87 | + | 'version': '0.1', | |
88 | + | 'fullname': self.conf['fullname'], | |
89 | + | 'lang': self.lang}) | |
90 | + | content = GettextFormat( | |
91 | + | {'file': oldpath, | |
92 | + | 'pot': self.potpath, | |
93 | + | 'version': '0.1', | |
94 | + | 'fullname': self.conf['fullname'], | |
95 | + | 'lang': self.lang}) | |
96 | + | newcontent.merge(content, callback) | |
97 | + | self.po = newcontent | |
98 | + | os.remove(oldpath) | |
99 | + | os.rename(self.popath, newname) | |
100 | + | self.popath = newname | |
115 | 101 | ||
116 | 102 | def send(self, interface): | |
117 | 103 | self.save() | |
… | |||
131 | 117 | self.po.save() | |
132 | 118 | ||
133 | 119 | def content(self): | |
134 | - | self.po = GettextFormat({'file': self.popath}, | |
135 | - | {'version': '0.1', | |
120 | + | self.po = GettextFormat( | |
121 | + | {'file': self.popath, | |
122 | + | 'pot': self.potpath, | |
123 | + | 'version': '0.1', | |
136 | 124 | 'fullname': self.conf['fullname'], | |
137 | 125 | 'lang': self.lang}) | |
138 | 126 | return {'default': self.po.content()} |
offlate/systems/transifex.py
1 | - | import requests | |
1 | + | """ The transifex system connector. """ | |
2 | + | ||
2 | 3 | import json | |
3 | - | from ruamel import yaml | |
4 | 4 | import os | |
5 | - | ||
5 | + | import requests | |
6 | 6 | from requests.auth import HTTPBasicAuth | |
7 | 7 | ||
8 | - | from pathlib import Path | |
9 | - | ||
10 | - | from ..formats.entry import JSONEntry | |
11 | - | ||
12 | - | def yaml_rec_load(path, source, dest): | |
13 | - | ans = [] | |
14 | - | for s in source: | |
15 | - | path2 = list(path) | |
16 | - | path2.append(s) | |
17 | - | if isinstance(source[s], str): | |
18 | - | ans.append({'path': path, 'id': s, 'source_string': source[s], 'translation': dest[s]}) | |
19 | - | else: | |
20 | - | ans.extend(yaml_rec_load(path2, source[s], dest[s])) | |
21 | - | return ans | |
22 | - | ||
23 | - | def yaml_rec_update(callback, source, old, new): | |
24 | - | ans = {} | |
25 | - | for i in new: | |
26 | - | o = '' | |
27 | - | s = '' | |
28 | - | n = new[i] | |
29 | - | try: | |
30 | - | s = source[i] | |
31 | - | except Exception: | |
32 | - | pass | |
33 | - | try: | |
34 | - | o = old[i] | |
35 | - | except Exception: | |
36 | - | pass | |
37 | - | if isinstance(n, str): | |
38 | - | if o == '': | |
39 | - | ans[i] = n | |
40 | - | elif n == '': | |
41 | - | ans[i] = o | |
42 | - | else: | |
43 | - | ans[i] = callback(s, o, n) | |
44 | - | else: | |
45 | - | ans[i] = yaml_rec_update(callback, s, o, n) | |
46 | - | return ans | |
47 | - | ||
48 | 8 | class TransifexProject: | |
49 | - | def __init__(self, conf, name, lang, data = {}): | |
50 | - | self.uri = "https://www.transifex.com" | |
9 | + | def __init__(self, conf, name, lang, data={}): | |
51 | 10 | self.conf = conf | |
52 | 11 | self.name = name | |
53 | 12 | self.lang = lang | |
54 | - | self.data = data | |
55 | 13 | self.basedir = '' | |
14 | + | self.data = data | |
56 | 15 | self.contents = {} | |
57 | 16 | ||
58 | 17 | def open(self, basedir): | |
… | |||
60 | 19 | with open(self.basedir + '/project.info') as f: | |
61 | 20 | self.files = json.load(f) | |
62 | 21 | self.slugs = [x['slug'] for x in self.files] | |
63 | - | self.loadContent() | |
64 | 22 | ||
65 | 23 | def initialize(self, basedir): | |
66 | 24 | self.basedir = basedir | |
… | |||
68 | 26 | with open(self.basedir + '/project.info', 'w') as f: | |
69 | 27 | f.write(json.dumps(self.files)) | |
70 | 28 | for slug in self.slugs: | |
71 | - | self.getFile(slug) | |
72 | - | self.loadContent() | |
73 | - | ||
74 | - | def update(self, callback): | |
75 | - | self.updateFileList() | |
76 | - | for ff in self.files: | |
77 | - | slug = ff['slug'] | |
78 | - | fname = self.filename(slug, False) | |
79 | - | sname = self.filename(slug, True) | |
80 | - | os.rename(fname, fname+'.old') | |
81 | - | self.getFile(slug) | |
82 | - | if ff['i18n_type'] == 'YML': | |
83 | - | with open(fname+'.old') as oldf: | |
84 | - | with open(fname) as newf: | |
85 | - | with open(sname) as sourcef: | |
86 | - | old = yaml.safe_load(oldf) | |
87 | - | new = yaml.safe_load(newf) | |
88 | - | source = yaml.safe_load(sourcef) | |
89 | - | realnew = yaml_rec_update(callback, source, old, new) | |
90 | - | with open(fname, 'w') as f: | |
91 | - | yaml.dump(realnew, f, Dumper=yaml.RoundTripDumper, | |
92 | - | allow_unicode=True) | |
29 | + | self.getFiles(slug) | |
93 | 30 | ||
94 | 31 | def updateFileList(self): | |
95 | 32 | self.files = [] | |
… | |||
102 | 39 | l = json.loads(ans.text) | |
103 | 40 | self.slugs = [x['slug'] for x in l] | |
104 | 41 | self.files = l | |
105 | - | ||
106 | - | def loadContent(self): | |
42 | + | ||
43 | + | def update(self, callback): | |
44 | + | self.updateFileList() | |
107 | 45 | for ff in self.files: | |
108 | - | with open(self.filename(ff['slug'], True)) as f: | |
109 | - | with open(self.filename(ff['slug'], False)) as f2: | |
110 | - | if ff['i18n_type'] == 'YML': | |
111 | - | source = yaml.safe_load(f) | |
112 | - | dest = yaml.safe_load(f2) | |
113 | - | lang1 = list(source.keys())[0] | |
114 | - | lang2 = list(dest.keys())[0] | |
115 | - | self.contents[ff['slug']] = \ | |
116 | - | yaml_rec_load([lang2], source[lang1], dest[lang2]) | |
46 | + | slug = ff['slug'] | |
47 | + | fname = self.filename(slug, False) | |
48 | + | sname = self.filename(slug, True) | |
49 | + | os.rename(fname, fname+'.old') | |
50 | + | os.rename(sname, sname+'.old') | |
51 | + | self.getFiles(slug) | |
52 | + | if ff['i18n_type'] == 'YML': | |
53 | + | oldformat = YamlFormat({'dest': fname+'.old', 'source': sname+'.old'}) | |
54 | + | currentformat = YamlFormat({'dest': fname, 'source': sname}) | |
55 | + | else: | |
56 | + | raise Exception("Unsupported format: " + ff['i18n_type']) | |
57 | + | currentformat.merge(oldformat) | |
58 | + | ||
59 | + | def filename(self, slug, is_source): | |
60 | + | ext = '' | |
61 | + | for ff in self.files: | |
62 | + | if ff['slug'] == slug: | |
63 | + | f = ff | |
64 | + | break | |
65 | + | if f['i18n_type'] == 'YML': | |
66 | + | ext = 'yml' | |
67 | + | else: | |
68 | + | raise Exception("Unsupported format: " + f['i18n_type']) | |
69 | + | return self.basedir + '/' + slug + ('.source' if is_source else '') + '.' + ext | |
117 | 70 | ||
118 | - | def getFile(self, slug): | |
71 | + | def getFiles(self, slug): | |
119 | 72 | ans = requests.get('https://www.transifex.com/api/2/project/'+ | |
120 | 73 | self.name+'/resource/'+slug+'/content', | |
121 | 74 | auth=HTTPBasicAuth('api', self.conf['token'])) | |
122 | 75 | if ans.status_code == 200: | |
123 | - | with open(self.filename(slug, True), 'w') as f: | |
124 | - | f.write(json.loads(ans.text)['content']) | |
76 | + | with open(self.filename(slug, True), 'wb') as f: | |
77 | + | f.write(json.loads(ans.text)['content'].encode('utf-8')) | |
125 | 78 | ||
126 | 79 | ans = requests.get('https://www.transifex.com/api/2/project/'+self.name+ | |
127 | 80 | '/resource/'+slug+'/translation/'+self.lang+'/?mode=translator', | |
128 | 81 | auth=HTTPBasicAuth('api', self.conf['token'])) | |
129 | 82 | if ans.status_code == 200: | |
130 | - | with open(self.filename(slug, False), 'w') as f: | |
131 | - | f.write(json.loads(ans.text)['content']) | |
83 | + | with open(self.filename(slug, False), 'wb') as f: | |
84 | + | f.write(json.loads(ans.text)['content'].encode('utf-8')) | |
132 | 85 | else: | |
133 | 86 | print(ans.text) | |
134 | 87 | ||
135 | - | def filename(self, slug, is_source): | |
136 | - | ext = '' | |
137 | - | for ff in self.files: | |
138 | - | if ff['slug'] == slug: | |
139 | - | f = ff | |
140 | - | break | |
141 | - | if f['i18n_type'] == 'YML': | |
142 | - | ext = 'yml' | |
143 | - | return self.basedir + '/' + slug + ('.source' if is_source else '') + '.' + ext | |
144 | - | ||
145 | 88 | def send(self, interface): | |
146 | - | self.save() | |
147 | - | for ff in self.files: | |
148 | - | print('{} => {}'.format(ff['slug'], ff['i18n_type'])) | |
149 | - | with open(self.filename(ff['slug'], False)) as f: | |
150 | - | content = f.read() | |
151 | - | sendcontent = {"content": content} | |
152 | - | ans = requests.put('https://www.transifex.com/api/2/project/'+ | |
153 | - | self.name+'/resource/'+ff['slug']+'/translation/'+self.lang+'/', | |
154 | - | json=sendcontent, auth=HTTPBasicAuth('api', self.conf['token'])) | |
155 | - | print(ans) | |
156 | - | print(ans.text) | |
89 | + | pass | |
157 | 90 | ||
158 | 91 | def save(self): | |
159 | - | for slug in self.slugs: | |
160 | - | data = {} | |
161 | - | for d in self.contents[slug]: | |
162 | - | path = d['path'] | |
163 | - | curr = data | |
164 | - | for p in path: | |
165 | - | if p in curr: | |
166 | - | curr = curr[p] | |
167 | - | else: | |
168 | - | curr[p] = {} | |
169 | - | curr = curr[p] | |
170 | - | curr[d['id']] = d['translation'] | |
171 | - | with open(self.filename(slug, False), 'w') as f: | |
172 | - | yaml.dump(data, f, Dumper=yaml.RoundTripDumper, allow_unicode=True) | |
92 | + | for slug in self.content: | |
93 | + | slug.save() | |
173 | 94 | ||
174 | 95 | def content(self): | |
175 | - | contents = {} | |
176 | - | for content in self.contents: | |
177 | - | contents[content] = [JSONEntry(x) for x in self.contents[content]] | |
178 | - | return contents | |
96 | + | content = {} | |
97 | + | self.content = [] | |
98 | + | for slug in self.files: | |
99 | + | if slug['i18n_type'] == 'YML': | |
100 | + | myslug = YamlFormat( | |
101 | + | {'dest': self.filename(slug['slug'], False), | |
102 | + | 'source': self.filename(slug['slug'], True)}) | |
103 | + | else: | |
104 | + | raise Exception("Unsupported format: " + slug['i18n_type']) | |
105 | + | self.content.append(myslug) | |
106 | + | content[slug['slug']] = myslug.content() | |
107 | + | return content |