~dholbach/help-app/1426304

« back to all changes in this revision

Viewing changes to edit-here/translations.py

  • Committer: Daniel Holbach
  • Date: 2015-03-03 12:34:46 UTC
  • Revision ID: daniel.holbach@canonical.com-20150303123446-7g0zu5tr9ebejlin
- create new class POFile which uses polib.pofile to let us search and 
  modify entries in translations, move all code related to that into 
  POFile
- instead of using po4a-updatepo, we use polib.pofile.merge
- clarify method and variable names (file vs fn (file name))
- save lines containing title: meta tag info into ./.title_lines

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
import codecs
2
2
import copy
3
3
import glob
 
4
import json
4
5
import os
5
6
import re
6
7
import shutil
64
65
        ret = self.run('po4a-gettextize', args)
65
66
        return (not ret)
66
67
 
67
 
    def updatepo(self, langs, documents):
68
 
        for po_fn in langs:
69
 
            args = []
70
 
            for document in documents:
71
 
                args += ['-m', document]
72
 
            args += ['-p', po_fn]
73
 
            ret = self.run('po4a-updatepo', args)
74
 
            if ret:
75
 
                return False
76
 
        return True
77
 
 
78
68
    def translate(self, doc, po_fn):
79
69
        args = [
80
70
            '-k', TRANSLATION_COMPLETION_PERCENTAGE,
85
75
        return self.run('po4a-translate', args, with_output=True)
86
76
 
87
77
 
 
78
class POFile(object):
 
79
    def __init__(self, po_fn):
 
80
        self.po_fn = po_fn
 
81
        self.pofile = polib.pofile(po_fn)
 
82
 
 
83
    def merge(self, pot_file_ob):
 
84
        self.pofile.merge(pot_file_ob)
 
85
 
 
86
    def save(self):
 
87
        self.pofile.save(self.po_fn)
 
88
 
 
89
    def find_in_msgid(self, find_str, translated=True, fuzzy=True,
 
90
                      untranslated=True):
 
91
        entries = []
 
92
        if translated:
 
93
            entries += self.pofile.translated_entries()
 
94
        if fuzzy:
 
95
            entries += self.pofile.fuzzy_entries()
 
96
        if untranslated:
 
97
            entries += self.pofile.untranslated_entries()
 
98
        results = []
 
99
        for entry in entries:
 
100
            if find_str in entry.msgid:
 
101
                results += [entry]
 
102
        return results
 
103
 
 
104
    def replace_title_lines(self):
 
105
        results = {}
 
106
        for entry in self.find_in_msgid('Title: '):
 
107
            if entry.msgid.startswith('Title: '):
 
108
                entry.msgid = entry.msgid.replace('Title: ', '')
 
109
                fn = entry.occurrences[0][0]
 
110
                results[fn] = entry.msgid
 
111
                if self.po_fn.endswith('.po'):
 
112
                    entry.msgstr = ''
 
113
        self.save()
 
114
        return results
 
115
 
 
116
    def rewrite_links(self, documents, bcp47):
 
117
        link_regex = r'\[.+?\]\(\{filename\}(.+?)\)'
 
118
        for entry in self.find_in_msgid('{filename}'):
 
119
            link_msgid = re.findall(link_regex, entry.msgid)[0]
 
120
            link_msgstr = list(re.findall(link_regex, entry.msgstr))
 
121
            translated_doc_fn = os.path.basename(
 
122
                documents.translated_doc_fn(link_msgid, bcp47))
 
123
            if not link_msgstr:
 
124
                entry.msgstr = entry.msgid
 
125
                link_msgstr = [link_msgid]
 
126
                entry.msgstr = entry.msgstr.replace(link_msgstr[0],
 
127
                                                    translated_doc_fn)
 
128
        self.save()
 
129
 
 
130
    def find_translated_title_line(self, original_title):
 
131
        for entry in self.find_in_msgid(original_title):
 
132
            if entry.msgstr:
 
133
                return entry.msgstr
 
134
            return entry.msgid
 
135
 
 
136
 
88
137
class PO(object):
89
138
    def __init__(self, po4a):
90
139
        self.translations_dir = os.path.abspath(os.path.join(PATH, '../po'))
92
141
        self.fake_po_fn = os.path.join(self.translations_dir,
93
142
                                       '%s.po' % self.fake_lang_code)
94
143
        self.pot_fn = os.path.join(self.translations_dir, 'help.pot')
 
144
        self.pot_file_ob = POFile(self.pot_fn)
95
145
        self.po4a = po4a
96
146
        self.langs = {}
97
147
        for po_fn in glob.glob(self.translations_dir+'/*.po'):
102
152
        self.langs[po_fn] = {
103
153
            'bcp47': find_bcp47_code(gettext_code),
104
154
            'gettext_code': gettext_code,
 
155
            'pofile': None,
105
156
        }
106
157
 
107
 
    def _remove_fake_po_fn(self):
 
158
    def _remove_fake_po_file(self):
108
159
        if os.path.exists(self.fake_po_fn):
109
160
            os.remove(self.fake_po_fn)
110
161
 
111
162
    def __del__(self):
112
 
        self._remove_fake_po_fn()
113
 
 
114
 
    def generate_pot_file(self, document_fns):
115
 
        if not self.po4a.gettextize(document_fns, self.pot_fn):
 
163
        self._remove_fake_po_file()
 
164
 
 
165
    def load_pofile(self, po_fn):
 
166
        if not self.langs[po_fn]['pofile']:
 
167
            self.langs[po_fn]['pofile'] = POFile(po_fn)
 
168
 
 
169
    def generate_pot_file(self, documents):
 
170
        if not self.po4a.gettextize(documents.docs, self.pot_fn):
116
171
            return False
117
 
        return self.po4a.updatepo(self.langs, document_fns)
 
172
        results = self.pot_file_ob.replace_title_lines()
 
173
        documents.add_title_lines(results)
 
174
        for po_fn in self.langs:
 
175
            self.load_pofile(po_fn)
 
176
            self.langs[po_fn]['pofile'].merge(self.pot_file_ob.pofile)
 
177
            self.langs[po_fn]['pofile'].replace_title_lines()
 
178
        return True
118
179
 
119
180
    # we generate a fake translation for en-US which is going to be
120
181
    # the default
121
182
    def generate_fake_pofile(self):
122
 
        self._remove_fake_po_fn()
 
183
        self._remove_fake_po_file()
123
184
        shutil.copy(self.pot_fn, self.fake_po_fn)
124
185
        self.add_language(self.fake_po_fn)
125
186
 
 
187
    def find_translated_title_line(self, original_title, po_fn):
 
188
        return self.langs[po_fn]['pofile'].find_translated_title_line(
 
189
            original_title)
 
190
 
126
191
    def rewrite_links(self, documents):
127
192
        for po_fn in self.langs:
128
 
            po_file = polib.pofile(po_fn)
129
 
            link_regex = r'\[.+?\]\(\{filename\}(.+?)\)'
130
 
            for entry_group in [po_file.translated_entries(),
131
 
                                po_file.fuzzy_entries(),
132
 
                                po_file.untranslated_entries()]:
133
 
                for entry in entry_group:
134
 
                    if '{filename}' in entry.msgid:
135
 
                        link_msgid = re.findall(link_regex, entry.msgid)[0]
136
 
                        link_msgstr = list(re.findall(link_regex,
137
 
                                                      entry.msgstr))
138
 
                        translated_doc_fn = os.path.basename(
139
 
                            documents.translated_doc_fn(
140
 
                                link_msgid, self.langs[po_fn]['bcp47']))
141
 
                        if not link_msgstr:
142
 
                            entry.msgstr = entry.msgid
143
 
                            link_msgstr = [link_msgid]
144
 
                        entry.msgstr = entry.msgstr.replace(link_msgstr[0],
145
 
                                                            translated_doc_fn)
146
 
            po_file.save(po_fn)
 
193
            self.load_pofile(po_fn)
 
194
            self.langs[po_fn]['pofile'].rewrite_links(
 
195
                documents, self.langs[po_fn]['bcp47'])
147
196
 
148
197
 
149
198
class Documents(object):
150
199
    def __init__(self):
151
 
        self.docs = []
 
200
        self.docs = {}
152
201
        for dirpath, dirnames, fns in os.walk(PATH):
153
202
            for fn in fns:
154
 
                self.docs += [os.path.join(dirpath, fn)]
 
203
                fn = os.path.relpath(os.path.join(dirpath, fn),
 
204
                                     os.path.join(PATH, '..'))
 
205
                self.docs[fn] = {
 
206
                    'title': None,
 
207
                }
 
208
        self.title_line_fn = os.path.abspath(
 
209
            os.path.join(PATH, '../../.title_lines'))
 
210
        self._read_saved_title_lines()
 
211
 
 
212
    def __del__(self):
 
213
        self._save_title_lines()
155
214
 
156
215
    def translated_doc_fn(self, fn, bcp47_code):
157
216
        match = [doc for doc in self.docs
163
222
 
164
223
    def _call_po4a_translate(self, doc, po_fn, po4a):
165
224
        res = po4a.translate(doc, po_fn)
166
 
        output = codecs.decode(res.communicate()[0])
167
 
        print(output)
168
 
        broken_title_line = [line for line in output.split('\n')
169
 
                             if line.lower().startswith('title:')][0]
170
 
        rest = [line for line in output.split('\n')
171
 
                if not line.lower().startswith('title')]
172
 
        output = '\n'.join(rest)
173
 
        return (broken_title_line, output)
 
225
        return codecs.decode(res.communicate()[0])
174
226
 
175
 
    def write_translated_markdown(self, langs, po4a):
176
 
        for po_fn in langs:
177
 
            for doc in self.docs:
178
 
                (broken_title_line, output) = \
179
 
                    self._call_po4a_translate(doc, po_fn, po4a)
180
 
                new_path = self.translated_doc_fn(doc, langs[po_fn]['bcp47'])
181
 
                text = "%s\nDate:\n\n" % (broken_title_line)
 
227
    def write_translated_markdown(self, po, po4a):
 
228
        for po_fn in po.langs:
 
229
            for doc_fn in self.docs:
 
230
                output = self._call_po4a_translate(doc_fn, po_fn, po4a)
 
231
                title_line = po.find_translated_title_line(
 
232
                    self.docs[doc_fn]['title'], po_fn)
 
233
                new_path = self.translated_doc_fn(doc_fn,
 
234
                                                  po.langs[po_fn]['bcp47'])
 
235
                text = "Title: %s\nDate:\n\n" % (title_line)
182
236
                text += output
183
237
                if os.path.exists(new_path):
184
238
                    os.remove(new_path)
187
241
                with open(new_path, 'w', encoding='utf-8') as f:
188
242
                    f.write(text)
189
243
 
 
244
    def add_title_lines(self, results):
 
245
        for doc_fn in results:
 
246
            self.docs[doc_fn]['title'] = results[doc_fn]
 
247
 
 
248
    def _save_title_lines(self):
 
249
        title_lines = []
 
250
        for doc_fn in self.docs:
 
251
            title_lines += [(doc_fn, self.docs[doc_fn]['title'])]
 
252
        if os.path.exists(self.title_line_fn):
 
253
            os.remove(self.title_line_fn)
 
254
        with open(self.title_line_fn, 'w') as f:
 
255
            f.write(json.dumps(title_lines))
 
256
 
 
257
    def _read_saved_title_lines(self):
 
258
        if os.path.exists(self.title_line_fn):
 
259
            title_lines = json.loads(open(self.title_line_fn).read())
 
260
            for entry in title_lines:
 
261
                self.docs[entry[0]]['title'] = entry[1]
 
262
 
190
263
 
191
264
class Translations(object):
192
265
    def __init__(self):
208
281
                os.remove(f)
209
282
 
210
283
    def generate_pot_file(self):
211
 
        return self.po.generate_pot_file(self.documents.docs)
 
284
        return self.po.generate_pot_file(self.documents)
212
285
 
213
286
    def generate_translations(self):
214
287
        self.po.generate_fake_pofile()
215
288
        self.po.rewrite_links(self.documents)
216
 
        self.documents.write_translated_markdown(self.po.langs, self.po4a)
 
289
        self.documents.write_translated_markdown(self.po, self.po4a)