1 | #! /usr/bin/python3 |
---|
2 | # -*- coding:utf-8 -*- |
---|
3 | |
---|
4 | import codecs |
---|
5 | import glob |
---|
6 | import os |
---|
7 | import re |
---|
8 | import shutil |
---|
9 | import sys |
---|
10 | |
---|
11 | try: |
---|
12 | import polib |
---|
13 | except: |
---|
14 | print("Requires polib. apt-get install python3-polib.") |
---|
15 | sys.exit(1) |
---|
16 | |
---|
17 | def get_translatables_from_file(input_file, trim_strs = True): |
---|
18 | """ Get all translatable strings (enclosed by '‌' characters) |
---|
19 | from the specfified file |
---|
20 | |
---|
21 | Args : html_file - the file with the translatable strings |
---|
22 | |
---|
23 | trim_strs - if True the translatable strings will have \n and |
---|
24 | redundant spaces between words removed |
---|
25 | |
---|
26 | Returns: a list of tuples, each of which contains a list of line numbers |
---|
27 | at which a translatable string was found, plus the string |
---|
28 | itself (without the ‌s). Example: |
---|
29 | [[12,43, 66], "System Requirements"] |
---|
30 | |
---|
31 | """ |
---|
32 | |
---|
33 | # use regular expressions to iterate through all the translatables |
---|
34 | p= re.compile("‌(.+?)‌", re.MULTILINE | re.DOTALL) |
---|
35 | lines = open(input_file, 'r') |
---|
36 | content = lines.read() |
---|
37 | line_nos = [] |
---|
38 | strings = [] |
---|
39 | for m in re.finditer (p, content): |
---|
40 | |
---|
41 | # get the translatable string and remove the "‌"s, \n chars |
---|
42 | # and redundant spaces etc. in the text |
---|
43 | text = content[m.start() : m.end()] |
---|
44 | text = text.replace("‌", "") |
---|
45 | |
---|
46 | if trim_strs: |
---|
47 | text = text.replace("\n", "") |
---|
48 | text = " ".join(text.split()) |
---|
49 | |
---|
50 | # if the text has already been encounted, add its line number to |
---|
51 | # the relevant entry in line_nos[] |
---|
52 | # otherwise add a new entry in line_nos and strings[] |
---|
53 | |
---|
54 | line_no = content.count("\n", 0, m.start()) + 1 |
---|
55 | if text in strings: |
---|
56 | line_nos[strings.index(text)].append(line_no) |
---|
57 | else: |
---|
58 | line_nos.append([line_no]) |
---|
59 | strings.append(text) |
---|
60 | |
---|
61 | # prepare and return the results |
---|
62 | translatables=[] |
---|
63 | for i in range(0, len(strings)): |
---|
64 | translatables.append([line_nos[i], strings[i]]) |
---|
65 | |
---|
66 | return translatables |
---|
67 | |
---|
68 | ############################################################################### |
---|
69 | def create_pot_file (input_file, pot_file, verbose): |
---|
70 | """ |
---|
71 | Create a .pot file from a translatables set of translatable strings |
---|
72 | |
---|
73 | Args: |
---|
74 | translatables: a list of tuples, each of which contain the line number |
---|
75 | at which a translatable string was found, plus the string |
---|
76 | itself |
---|
77 | pot_file : the filename of the .pot to create |
---|
78 | verbose : whether to display progress info |
---|
79 | """ |
---|
80 | |
---|
81 | translatables = get_translatables_from_file(input_file) |
---|
82 | |
---|
83 | # check if the file exists. If it does, open it, otherwise create a new file |
---|
84 | new_file = not os.path.exists(pot_file) |
---|
85 | if new_file: |
---|
86 | pofile = polib.POFile() |
---|
87 | pofile.metadata= { |
---|
88 | 'Project-Id-Version': 'Ubuntu MATE Welcome', |
---|
89 | "Report-Msgid-Bugs-To ": "you@example.com", |
---|
90 | "POT-Creation-Date": "2016-02-27 12:48+0100", |
---|
91 | "PO-Revision-Date": "YEAR-MO-DA HO:MI+ZONE", |
---|
92 | "Last-Translator": "FULL NAME <EMAIL@ADDRESS>", |
---|
93 | "Language-Team:": "LANGUAGE <LL@li.org>", |
---|
94 | "MIME-Version": "1.0", |
---|
95 | "Content-Type": "text/plain; charset=UTF-8", |
---|
96 | "Content-Transfer-Encoding": "8bit",} |
---|
97 | else: |
---|
98 | pofile = polib.pofile(pot_file) |
---|
99 | |
---|
100 | source_path, source_file = os.path.split(input_file) |
---|
101 | for translatable in translatables: |
---|
102 | entry = polib.POEntry( |
---|
103 | msgid = translatable[1], |
---|
104 | msgstr = "", |
---|
105 | occurrences = [(source_file, "%s" %str(translatable[0]).strip("[]"))]) |
---|
106 | |
---|
107 | if new_file: |
---|
108 | pofile.append (entry) |
---|
109 | if verbose: |
---|
110 | print ('Added "%s" to pot file' %translatable[1]) |
---|
111 | else: |
---|
112 | # add the entry only if it isn't already present... |
---|
113 | present = False |
---|
114 | for en in pofile: |
---|
115 | if en.msgid == entry.msgid: |
---|
116 | present = True |
---|
117 | break |
---|
118 | |
---|
119 | if not present: |
---|
120 | pofile.append(entry) |
---|
121 | if verbose: |
---|
122 | print ('Added "%s" to pot file' %translatable[1]) |
---|
123 | else: |
---|
124 | if verbose: |
---|
125 | print ('Did not add "%s" - already present.' %translatable[1]) |
---|
126 | |
---|
127 | if verbose: |
---|
128 | print ('Saving pot file to %s' %pot_file) |
---|
129 | |
---|
130 | pofile.save(pot_file) |
---|
131 | if verbose: |
---|
132 | print ("Saved") |
---|
133 | |
---|
134 | ################################################################################### |
---|
135 | def translate_file(in_file, in_po, out_file, verbose): |
---|
136 | """ Translate a file from English to another language |
---|
137 | |
---|
138 | For every translatable string in in_file, replace the corresponding |
---|
139 | text in the input file with the appropriate translation from the .po |
---|
140 | file. When all translations are done, write the resulting html to the |
---|
141 | output file |
---|
142 | |
---|
143 | Args: |
---|
144 | in_file: the original filename whose contents are in English within |
---|
145 | ‌ enclosures |
---|
146 | in_po : the filename of the .po file to use for the translation |
---|
147 | out_file : the filename that the translation gets written to |
---|
148 | |
---|
149 | """ |
---|
150 | |
---|
151 | # get the translatable strings from the input file |
---|
152 | translatables = get_translatables_from_file(in_file, trim_strs=False) |
---|
153 | |
---|
154 | # read the entitrecontents of the file |
---|
155 | content = open(in_file, 'r').read() |
---|
156 | |
---|
157 | # read the po file |
---|
158 | po_file = polib.pofile(in_po) |
---|
159 | |
---|
160 | # for every translatable string, try to find and replace it with the appropriate |
---|
161 | # translated |
---|
162 | for translatable in translatables: |
---|
163 | |
---|
164 | #can we find the translatable text in the input file? |
---|
165 | if translatable[1] in content: |
---|
166 | |
---|
167 | # remove \n and redundant spaces between words from the translatable string, so that |
---|
168 | # it will match the entry in the .po file |
---|
169 | trim_text = translatable[1].replace("\n", "") |
---|
170 | trim_text = " ".join(trim_text.split()) |
---|
171 | |
---|
172 | # iterate through the po entries looking for the one which matches the current translatable |
---|
173 | for entry in po_file: |
---|
174 | if (entry.msgid == trim_text): |
---|
175 | |
---|
176 | if (entry.msgstr !=""): |
---|
177 | |
---|
178 | # replace the English text with the tranlated text. |
---|
179 | # NOTE: ALL occurrences of the English text are replaced throughout the entire |
---|
180 | # file |
---|
181 | |
---|
182 | content = content.replace("%s%s%s" %("‌", translatable[1], "‌"), entry.msgstr) |
---|
183 | if verbose: |
---|
184 | print ("Translated %s >> %s" %(trim_text, entry.msgstr)) |
---|
185 | else: |
---|
186 | # there's no translation, so instead just remove the ‌ characters from the string |
---|
187 | content = content.replace("%s%s%s" %("‌", translatable[1], "‌"), entry.msgid) |
---|
188 | if verbose: |
---|
189 | print("No translation for %s, so ‌ characters removed" %entry.msgid) |
---|
190 | break |
---|
191 | |
---|
192 | else: |
---|
193 | if verbose: |
---|
194 | print ("WARNING: translatable string %s not found" %translatable[1]) |
---|
195 | |
---|
196 | outfile = codecs.open(out_file, 'w', encoding='utf-8') |
---|
197 | outfile.write(content) |
---|
198 | outfile.close() |
---|
199 | |
---|
200 | ########################################################################## |
---|
201 | def create_dummy_translations(in_po, out_po, verbose): |
---|
202 | """ Take the input po file and set the translations to be the reverse of the original string |
---|
203 | and write the results to a new po_file |
---|
204 | |
---|
205 | Args: |
---|
206 | in_po : the source .po |
---|
207 | out_po : the po file where the dummy translations are to be written |
---|
208 | verbose: let the user know what's going on... |
---|
209 | """ |
---|
210 | |
---|
211 | |
---|
212 | pofile = polib.pofile(in_po) |
---|
213 | for entry in pofile: |
---|
214 | entry.msgstr = entry.msgid[::-1] |
---|
215 | if verbose: |
---|
216 | print ('Setting translation of "%s" to "%s"' %(entry.msgid, entry.msgstr)) |
---|
217 | |
---|
218 | if verbose: |
---|
219 | print ('Saving to %s' %out_po) |
---|
220 | pofile.save(out_po) |
---|
221 | if verbose: |
---|
222 | print('Saved') |
---|
223 | |
---|
224 | ######################################################################## |
---|
225 | def create_all_pot_files(slide_dir, po_dir, verbose): |
---|
226 | """ Create .pot files for all the html files in slide_dir and place them |
---|
227 | in the po_dir directory |
---|
228 | |
---|
229 | Args: slide_dir - the directory containing the html files |
---|
230 | po_dir - the directory where the .pot files are to be created |
---|
231 | verbose - let the user know what's happening |
---|
232 | |
---|
233 | """ |
---|
234 | |
---|
235 | # if there isnt already a 'po' directory in ./data, create one |
---|
236 | if not os.path.exists(po_dir): |
---|
237 | os.mkdir(po_dir) |
---|
238 | if verbose: |
---|
239 | print ("Created %s directory" %po_dir) |
---|
240 | |
---|
241 | template_slides = glob.glob(os.path.join(slide_dir, '*.html')) |
---|
242 | |
---|
243 | # create a list of tuples containing the full slide path and filename, and the slide |
---|
244 | # names minus the .html extension |
---|
245 | slide_info=[] |
---|
246 | for slide in template_slides: |
---|
247 | slide_filename = slide |
---|
248 | slide_name =(os.path.splitext(os.path.split(slide)[1])[0]) |
---|
249 | slide_info.append([slide_filename, slide_name]) |
---|
250 | |
---|
251 | # if necessary create directories under po_dir for the slides |
---|
252 | for slide in slide_info: |
---|
253 | if not os.path.exists(os.path.join(po_dir, slide[1])): |
---|
254 | os.mkdir(os.path.join(po_dir, slide[1])) |
---|
255 | if verbose: |
---|
256 | print ("Created directory %s" %os.path.join(po_dir, slide[1])) |
---|
257 | |
---|
258 | # now create the .pot files |
---|
259 | for slide in slide_info: |
---|
260 | pot_file = os.path.join(po_dir, slide[1]) |
---|
261 | pot_file = os.path.join(pot_file, slide[1] + ".pot") |
---|
262 | if os.path.exists(pot_file): |
---|
263 | # move the existing file out of the way |
---|
264 | shutil.copyfile (pot_file, pot_file + ".old") |
---|
265 | |
---|
266 | # create the pot file |
---|
267 | create_pot_file(slide[0], pot_file, False) |
---|
268 | if verbose: |
---|
269 | print ("Created %s" %pot_file) |
---|
270 | |
---|
271 | print ("All .pots created.") |
---|
272 | |
---|
273 | ################################################################################################# |
---|
274 | def create_i18n_slides(slide_dir, po_dir, i18n_dir, verbose): |
---|
275 | """ Create translations of the html slides in source_dir, using the .po files in po_dir/<slide name> |
---|
276 | and write them to the i18n directory in i18n_dir/<locale>/ |
---|
277 | |
---|
278 | Args: |
---|
279 | slide_dir : the location of the html files |
---|
280 | po_dir : the location of the po files containing translations |
---|
281 | i18n_diir : the location where translatied html gets written |
---|
282 | verbose : let the user know what's going on |
---|
283 | """ |
---|
284 | |
---|
285 | |
---|
286 | # make the 'i18n' directory if not already present |
---|
287 | if not os.path.exists(i18n_dir): |
---|
288 | os.mkdir(i18n_dir) |
---|
289 | |
---|
290 | template_slides = glob.glob(os.path.join(slide_dir, '*.html')) |
---|
291 | |
---|
292 | for slide in template_slides: |
---|
293 | slide_name = (os.path.splitext(os.path.split(slide)[1])[0]) |
---|
294 | print("Working on slide: %s...." %slide_name) |
---|
295 | |
---|
296 | slide_po = os.path.join(po_dir, slide_name) |
---|
297 | locales = glob.glob(os.path.join(slide_po, '*.po')) |
---|
298 | for locale_file in sorted(locales): |
---|
299 | locale_name = os.path.basename(locale_file).replace(".po", "") |
---|
300 | |
---|
301 | locale_slides = os.path.join(i18n_dir, locale_name) |
---|
302 | #make the locale directory if it doesn't already exists |
---|
303 | if not os.path.exists(locale_slides): |
---|
304 | os.mkdir(locale_slides) |
---|
305 | |
---|
306 | output_slide = os.path.join(locale_slides, slide_name + ".html") |
---|
307 | |
---|
308 | if os.path.exists(output_slide): |
---|
309 | os.remove(output_slide) |
---|
310 | |
---|
311 | translate_file(slide, locale_file, output_slide, False) |
---|
312 | if verbose: |
---|
313 | print ("Translated %s to %s locale" %(slide_name,locale_name)) |
---|
314 | |
---|
315 | print ('All slides translated') |
---|
316 | |
---|
317 | create_all_pots = False |
---|
318 | create_pot = False |
---|
319 | translate_all = False |
---|
320 | translate = False |
---|
321 | reverse_po = False |
---|
322 | input_file = '' |
---|
323 | output_file = '' |
---|
324 | po_file = '' |
---|
325 | verbose = False |
---|
326 | |
---|
327 | commands=0 |
---|
328 | for arg in sys.argv: |
---|
329 | if arg == '--help': |
---|
330 | print('\nUsage: edgar-allan [command] [arguments]') |
---|
331 | print('\nCommand may be one of the following:') |
---|
332 | print(' create-all-pots Create .pot files for each html file in the data directory and write') |
---|
333 | print(' them to the data/po directory') |
---|
334 | print(' create-pot Create a .pot file from an input file containing ‌ enclosed') |
---|
335 | print(' strings.') |
---|
336 | print(' translate-all Create translations for all slides in the data directory using the') |
---|
337 | print(' available .po files in the data/po/<slide>/ directory') |
---|
338 | print(' translate Translate an input file using a specified .po file and write the') |
---|
339 | print(' result to an output file.') |
---|
340 | print(' po Take a po file and set all of the translations within it to be') |
---|
341 | print(' the reverse of the original string and write the result to an output') |
---|
342 | print(' file (can be useful when testing the translation function.') |
---|
343 | print('Arguments:') |
---|
344 | print(' --input=<filename> The file for input to the create-pot and translate commands.') |
---|
345 | print(' --po-file=<filename> The .po file to use when translating a file.') |
---|
346 | print(' --output=<filename> The file to be created by the create-pot and translate commands') |
---|
347 | print(' --verbose, -v Display detailed output') |
---|
348 | print('') |
---|
349 | exit() |
---|
350 | |
---|
351 | if arg == 'create-all-pots': |
---|
352 | create_all_pots = True |
---|
353 | commands+=1 |
---|
354 | |
---|
355 | if arg == 'translate-all': |
---|
356 | translate_all = True |
---|
357 | commands+=1 |
---|
358 | |
---|
359 | if arg == 'create-pot': |
---|
360 | create_pot = True |
---|
361 | commands+=1 |
---|
362 | |
---|
363 | if arg == 'translate': |
---|
364 | translate = True |
---|
365 | commands+=1 |
---|
366 | |
---|
367 | if arg=='po': |
---|
368 | reverse_po = True |
---|
369 | commands+=1 |
---|
370 | |
---|
371 | if arg.startswith('--input='): |
---|
372 | input_file = arg.split('--input=')[1] |
---|
373 | |
---|
374 | if arg.startswith('--output='): |
---|
375 | output_file = arg.split('--output=')[1] |
---|
376 | |
---|
377 | if arg.startswith('--po-file='): |
---|
378 | po_file = arg.split('--po-file=')[1] |
---|
379 | |
---|
380 | if arg == '--verbose' or arg == '-v': |
---|
381 | verbose = True |
---|
382 | |
---|
383 | if commands>1: |
---|
384 | print('Specify only one of the create-pot, translate, po... commands') |
---|
385 | exit() |
---|
386 | |
---|
387 | if commands==0: |
---|
388 | print('No command specified - use --help to see what''s available') |
---|
389 | exit() |
---|
390 | |
---|
391 | # check that commands which need arguments have them |
---|
392 | cmd_check = None |
---|
393 | if translate: |
---|
394 | cmd_check = 'translate' |
---|
395 | elif create_pot: |
---|
396 | cmd_check = 'create-pot' |
---|
397 | elif reverse_po: |
---|
398 | cmd_check = 'po' |
---|
399 | |
---|
400 | if cmd_check is not None: |
---|
401 | if (input_file == ''): |
---|
402 | print('No input file specifed for %s command' %cmd_check) |
---|
403 | exit() |
---|
404 | |
---|
405 | if (output_file ==''): |
---|
406 | print('No output file specified for %s command' %cmd_check) |
---|
407 | exit() |
---|
408 | |
---|
409 | if translate and (po_file==""): |
---|
410 | print('No .po file specified for %s command' %cmd_check) |
---|
411 | exit() |
---|
412 | |
---|
413 | source_dir = '.' |
---|
414 | build_slides = os.path.join(source_dir, 'data') |
---|
415 | po_dir = os.path.join(build_slides, 'po') |
---|
416 | i18n_dir =os.path.join(source_dir, 'i18n') |
---|
417 | |
---|
418 | if create_all_pots: |
---|
419 | create_all_pot_files(build_slides, po_dir, verbose) |
---|
420 | elif translate_all: |
---|
421 | create_i18n_slides(build_slides, po_dir, i18n_dir, verbose) |
---|
422 | elif create_pot: |
---|
423 | create_pot_file (input_file, output_file, verbose) |
---|
424 | elif translate: |
---|
425 | translate_file (input_file, po_file, output_file, verbose) |
---|
426 | elif reverse_po: |
---|
427 | create_dummy_translations(input_file, output_file, verbose) |
---|
428 | |
---|
429 | |
---|
430 | |
---|