File: generate_translations.py

package info (click to toggle)
openshot-qt 3.1.1%2Bdfsg1-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 166,336 kB
  • sloc: python: 475,672; javascript: 34,891; xml: 3,397; makefile: 218; sh: 156
file content (500 lines) | stat: -rwxr-xr-x 21,013 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
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
312
313
314
315
316
317
318
319
320
321
322
323
324
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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
#!/usr/bin/python3
r"""
 @file
 @brief This file updates the OpenShot.POT (language translation template) by scanning all source files.
 @author Jonathan Thomas <jonathan@openshot.org>

 This file helps you generate the POT file that contains all of the translatable
 strings / text in OpenShot.  Because some of our text is in custom XML files,
 the xgettext command can't correctly generate the POT file.  Thus... the
 existence of this file. =)

 Command to create the individual language PO files (Ascii files)
		$ msginit --input=OpenShot.pot --locale=fr_FR
		$ msginit --input=OpenShot.pot --locale=es

 Command to update the PO files (if text is added or changed)
		$ msgmerge en_US.po OpenShot.pot -U
		$ msgmerge es.po OpenShot.pot -U

 Command to compile the Ascii PO files into binary MO files
		$ msgfmt en_US.po --output-file=en_US/LC_MESSAGES/OpenShot.mo
		$ msgfmt es.po --output-file=es/LC_MESSAGES/OpenShot.mo

 Command to compile all PO files in a folder
		$ find -iname "*.po" -exec msgfmt {} -o {}.mo \;

 Command to combine the 2 pot files into 1 file
       $ msgcat ~/openshot/locale/OpenShot/OpenShot_source.pot ~/openshot/openshot/locale/OpenShot/OpenShot_glade.pot -o ~/openshot/main/locale/OpenShot/OpenShot.pot

 @section LICENSE

 Copyright (c) 2008-2018 OpenShot Studios, LLC
 (http://www.openshotstudios.com). This file is part of
 OpenShot Video Editor (http://www.openshot.org), an open-source project
 dedicated to delivering high quality video editing and animation solutions
 to the world.

 OpenShot Video Editor is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 OpenShot Video Editor is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with OpenShot Library.  If not, see <http://www.gnu.org/licenses/>.
 """

import shutil
import datetime
import os
import subprocess
import sys
import re
import json

# Try to get the security-patched XML functions from defusedxml
try:
  from defusedxml import minidom as xml
except ImportError:
  from xml.dom import minidom as xml

import openshot

# Get the absolute path of this project
path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
if path not in sys.path:
    sys.path.append(path)

import classes.info as info
from classes.logger import log
from classes.effect_init import effect_options

# get the path of the main OpenShot folder
language_folder_path = os.path.dirname(os.path.abspath(__file__))
openshot_path = os.path.dirname(language_folder_path)
effects_path = os.path.join(openshot_path, 'effects')
blender_path = os.path.join(openshot_path, 'blender')
transitions_path = os.path.join(openshot_path, 'transitions')
titles_path = os.path.join(openshot_path, 'titles')
export_path = os.path.join(openshot_path, 'presets')
windows_ui_path = os.path.join(openshot_path, 'windows', 'ui')

log.info("-----------------------------------------------------")
log.info(" Creating temp POT files")
log.info("-----------------------------------------------------")

# create empty temp POT files
temp_files = ['OpenShot_source.pot', 'OpenShot_glade.pot', 'OpenShot_effects.pot', 'OpenShot_export.pot',
              'OpenShot_transitions.pot', 'OpenShot_QtUi.pot']
for temp_file_name in temp_files:
    temp_file_path = os.path.join(language_folder_path, temp_file_name)
    if os.path.exists(temp_file_path):
        os.remove(temp_file_path)
    f = open(temp_file_path, "w")
    f.close()

log.info("-----------------------------------------------------")
log.info(" Using xgettext to generate .py POT files")
log.info("-----------------------------------------------------")

# Generate POT for Source Code strings (i.e. strings marked with a _("translate me"))
subprocess.call(r'find %s -iname "*.py" -exec xgettext -j -o %s --keyword=_ {} \;' % (
openshot_path, os.path.join(language_folder_path, 'OpenShot_source.pot')), shell=True)

log.info("-----------------------------------------------------")
log.info(" Using Qt's lupdate to generate .ui POT files")
log.info("-----------------------------------------------------")

# Generate POT for Qt *.ui files (which require the lupdate command, and ts2po command)
os.chdir(windows_ui_path)
subprocess.call('lupdate *.ui -ts %s' % (os.path.join(language_folder_path, 'OpenShot_QtUi.ts')), shell=True)
subprocess.call('lupdate *.ui -ts %s' % (os.path.join(language_folder_path, 'OpenShot_QtUi.pot')), shell=True)
os.chdir(language_folder_path)

# Rewrite the UI POT, removing msgctxt
output = open(os.path.join(language_folder_path, "clean.po"), 'w')
for line in open(os.path.join(language_folder_path, 'OpenShot_QtUi.pot'), 'r'):
    if not line.startswith('msgctxt'):
        output.write(line)
# Overwrite original PO file
output.close()
shutil.copy(os.path.join(language_folder_path, "clean.po"), os.path.join(language_folder_path, 'OpenShot_QtUi.pot'))
os.remove(os.path.join(language_folder_path, "clean.po"))

# Remove duplicates (if any found)
subprocess.call('msguniq %s --use-first -o %s' % (os.path.join(language_folder_path, 'OpenShot_QtUi.pot'),
                                                  os.path.join(language_folder_path, 'clean.po')), shell=True)
shutil.copy(os.path.join(language_folder_path, "clean.po"), os.path.join(language_folder_path, 'OpenShot_QtUi.pot'))
os.remove(os.path.join(language_folder_path, "clean.po"))


log.info("-----------------------------------------------------")
log.info(" Updating auto created POT files to set CharSet")
log.info("-----------------------------------------------------")

temp_files = ['OpenShot_source.pot', 'OpenShot_glade.pot']
for temp_file in temp_files:
    # get the entire text
    f = open(os.path.join(language_folder_path, temp_file), "r")
    # read entire text of file
    entire_source = f.read()
    f.close()

    # replace charset
    entire_source = entire_source.replace("charset=CHARSET", "charset=UTF-8")

    # Create Updated POT Output File
    if os.path.exists(os.path.join(language_folder_path, temp_file)):
        os.remove(os.path.join(language_folder_path, temp_file))
    f = open(os.path.join(language_folder_path, temp_file), "w")
    f.write(entire_source)
    f.close()

log.info("-----------------------------------------------------")
log.info(" Scanning custom XML files and finding text")
log.info("-----------------------------------------------------")

# Loop through the Effects XML
effects_text = {}
for file in os.listdir(effects_path):
    if os.path.isfile(os.path.join(effects_path, file)):
        # load xml effect file
        full_file_path = os.path.join(effects_path, file)
        xmldoc = xml.parse(os.path.join(effects_path, file))

        # add text to list
        effects_text[xmldoc.getElementsByTagName("title")[0].childNodes[0].data] = full_file_path
        effects_text[xmldoc.getElementsByTagName("description")[0].childNodes[0].data] = full_file_path

        # get params
        params = xmldoc.getElementsByTagName("param")

        # Loop through params
        for param in params:
            if param.attributes["title"]:
                effects_text[param.attributes["title"].value] = full_file_path

# Append on properties from libopenshot
objects = [openshot.Clip(), openshot.Bars(), openshot.Blur(), openshot.Brightness(),
           openshot.ChromaKey(), openshot.ColorShift(), openshot.Crop(), openshot.Deinterlace(), openshot.Hue(), openshot.Mask(),
           openshot.Negate(), openshot.Pixelate(), openshot.Saturation(), openshot.Shift(), openshot.Wave()]

# Loop through each libopenshot object
for object in objects:
    props = json.loads(object.PropertiesJSON(1))

    # Loop through props
    for key in props.keys():
        object = props[key]
        if "name" in object:
            effects_text[object["name"]] = "libopenshot (Clip Properties)"
        if "choices" in object:
            for choice in object["choices"]:
                effects_text[choice["name"]] = "libopenshot (Clip Properties)"

# Append Effect Init Data
# Loop through props
for effect in effect_options:
    for param in effect_options[effect]:
        if "title" in param:
            effects_text[param["title"]] = "effect_init (Effect parameter for %s)" % effect
        if "values" in param:
            for value in param["values"]:
                effects_text[value["name"]] = "effect_init (Effect parameter for %s)" % effect

# Append Effect Meta Data
e = openshot.EffectInfo()
props = json.loads(e.Json())

# Loop through props
for effect in props:
    if "name" in effect:
        effects_text[effect["name"]] = "libopenshot (Effect Metadata)"
    if "description" in effect:
        effects_text[effect["description"]] = "libopenshot (Effect Metadata)"

# Append Emoji Data
emoji_text = { "translator-credits": "Translator credits to be translated by LaunchPad" }
emoji_metadata_path = os.path.join(info.PATH, "emojis", "data", "openmoji-optimized.json")
emoji_ignore_keys = ("Keyboard", "Sunset", "Key", "Right arrow", "Left arrow", "Bubbles",
                     "Twitter", "Instagram", "Scale", "Simple", "Close", "Forward", "Copy",
                     "Filter", "Details")
with open(emoji_metadata_path, 'r', encoding="utf-8") as f:
    emoji_metadata = json.load(f)

    # Loop through props
    for filename, emoji in emoji_metadata.items():
        emoji_name = emoji["annotation"].capitalize()
        emoji_group = emoji["group"].split('-')[0].capitalize()
        if "annotation" in emoji and emoji_name not in emoji_ignore_keys:
            emoji_text[emoji_name] = "Emoji Metadata (Displayed Name)"
        if "group" in emoji and emoji_group not in effects_text and emoji_group not in emoji_ignore_keys:
            emoji_text[emoji_group] = "Emoji Metadata (Group Filter name)"

# Loop through the Blender XML
blender_text = { "translator-credits": "Translator credits to be translated by LaunchPad" }
blender_ignore_keys = ("Title", "Alpha", "Blur")
for file in os.listdir(blender_path):
    if os.path.isfile(os.path.join(blender_path, file)):
        # load xml effect file
        full_file_path = os.path.join(blender_path, file)
        xmldoc = xml.parse(os.path.join(blender_path, file))

        # add text to list
        translation_key = xmldoc.getElementsByTagName("title")[0].childNodes[0].data
        if translation_key not in blender_ignore_keys:
            blender_text[translation_key] = full_file_path

        # get params
        params = xmldoc.getElementsByTagName("param")

        # Loop through params
        for param in params:
            if param.attributes["title"]:
                translation_key = param.attributes["title"].value
                if translation_key not in blender_ignore_keys:
                    blender_text[param.attributes["title"].value] = full_file_path

# Loop through the Export Settings XML
export_text = {}
for file in os.listdir(export_path):
    if os.path.isfile(os.path.join(export_path, file)):
        # load xml export file
        full_file_path = os.path.join(export_path, file)
        xmldoc = xml.parse(os.path.join(export_path, file))

        # add text to list
        export_text[xmldoc.getElementsByTagName("type")[0].childNodes[0].data] = full_file_path
        export_text[xmldoc.getElementsByTagName("title")[0].childNodes[0].data] = full_file_path

# Loop through Settings
settings_file = open(os.path.join(info.PATH, 'settings', '_default.settings'), 'r').read()
settings = json.loads(settings_file)
category_names = []
for setting in settings:
    if "type" in setting and setting["type"] != "hidden":
        # Add visible settings
        export_text[setting["title"]] = "Settings for %s" % setting["setting"]
    if "type" in setting and setting["type"] != "hidden":
        # Add visible category names
        if setting["category"] not in category_names:
            export_text[setting["category"]] = "Settings Category for %s" % setting["category"]
            category_names.append(setting["category"])
        if "translate_values" in setting and setting.get("translate_values"):
            # Add translatable dropdown keys
            for value in setting.get("values", []):
                export_text[value["name"]] = "Settings for %s" % setting["setting"]

# Loop through transitions and add to POT file
transitions_text = { "translator-credits": "Translator credits to be translated by LaunchPad" }
transitions_ignore_keys = ("Common", "Fade")
for file in os.listdir(transitions_path):
    # load xml export file
    full_file_path = os.path.join(transitions_path, file)
    (fileBaseName, fileExtension) = os.path.splitext(file)

    # get transition name
    name = fileBaseName.replace("_", " ").capitalize()

    # add text to list
    if name not in transitions_ignore_keys:
        transitions_text[name] = full_file_path

    # Look in sub-folders
    for sub_file in os.listdir(full_file_path):
        # load xml export file
        full_subfile_path = os.path.join(full_file_path, sub_file)
        fileBaseName = os.path.splitext(sub_file)[0]

        # split the name into parts (looking for a number)
        suffix_number = None
        name_parts = fileBaseName.split("_")
        if name_parts[-1].isdigit():
            suffix_number = name_parts[-1]

        # get transition name
        name = fileBaseName.replace("_", " ").capitalize()

        # replace suffix number with placeholder (if any)
        if suffix_number:
            name = name.replace(suffix_number, "%s")

        # add text to list
        if name not in transitions_ignore_keys:
            transitions_text[name] = full_subfile_path

# Loop through titles and add to POT file
for sub_file in os.listdir(titles_path):
    # load xml export file
    full_subfile_path = os.path.join(titles_path, sub_file)
    fileBaseName = os.path.splitext(sub_file)[0]

    # split the name into parts (looking for a number)
    suffix_number = None
    name_parts = fileBaseName.split("_")
    if name_parts[-1].isdigit():
        suffix_number = name_parts[-1]

    # get transition name
    name = fileBaseName.replace("_", " ").capitalize()

    # replace suffix number with placeholder (if any)
    if suffix_number:
        name = name.replace(suffix_number, "%s")

    # add text to list
    transitions_text[name] = full_subfile_path


log.info("-----------------------------------------------------")
log.info(" Creating the custom XML POT files")
log.info("-----------------------------------------------------")

# header of POT file
header_text = ""
header_text = header_text + '# OpenShot Video Editor POT Template File.\n'
header_text = header_text + '# Copyright (C) 2008-2018 OpenShot Studios, LLC\n'
header_text = header_text + '# This file is distributed under the same license as OpenShot.\n'
header_text = header_text + '# Jonathan Thomas <Jonathan.Oomph@gmail.com>, 2018.\n'
header_text = header_text + '#\n'
header_text = header_text + '#, fuzzy\n'
header_text = header_text + 'msgid ""\n'
header_text = header_text + 'msgstr ""\n'
header_text = header_text + '"Project-Id-Version: OpenShot Video Editor (version: %s)\\n"\n' % info.VERSION
header_text = header_text + '"Report-Msgid-Bugs-To: Jonathan Thomas <Jonathan.Oomph@gmail.com>\\n"\n'
header_text = header_text + '"POT-Creation-Date: %s\\n"\n' % datetime.datetime.now()
header_text = header_text + '"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"\n'
header_text = header_text + '"Last-Translator: Jonathan Thomas <Jonathan.Oomph@gmail.com>\\n"\n'
header_text = header_text + '"Language-Team: https://translations.launchpad.net/+groups/launchpad-translators\\n"\n'
header_text = header_text + '"MIME-Version: 1.0\\n"\n'
header_text = header_text + '"Content-Type: text/plain; charset=UTF-8\\n"\n'
header_text = header_text + '"Content-Transfer-Encoding: 8bit\\n"\n'

# Create POT files for the custom text (from our XML files)
temp_files = [['OpenShot_effects.pot', effects_text],
              ['OpenShot_export.pot', export_text],
              ['OpenShot_transitions.pot', transitions_text],
              ['OpenShot_emojis.pot', emoji_text],
              ['OpenShot_blender.pot', blender_text]
              ]
for temp_file, text_dict in temp_files:
    f = open(temp_file, "w")

    # write header
    f.write(header_text)

    # loop through each line of text
    for k, v in text_dict.items():
        if k:
            f.write('\n')
            f.write('#: %s\n' % v)
            f.write('msgid "%s"\n' % k)
            f.write('msgstr ""\n')

    # close file
    f.close()

log.info("-----------------------------------------------------")
log.info(" Combine all temp POT files using msgcat command ")
log.info(" (this removes dupes) ")
log.info("-----------------------------------------------------")

temp_files = ['OpenShot_source.pot', 'OpenShot_glade.pot', 'OpenShot_effects.pot',
              'OpenShot_export.pot', 'OpenShot_QtUi.pot']
command = "msgcat"
for temp_file in temp_files:
    # append files
    command = command + " " + os.path.join(language_folder_path, temp_file)
command = command + " -o " + os.path.join(language_folder_path, "OpenShot", "OpenShot.pot")

log.info(command)

# merge all 4 temp POT files
subprocess.call(command, shell=True)

log.info("-----------------------------------------------------")
log.info(" Create FINAL POT File from all temp POT files ")
log.info("-----------------------------------------------------")

# get the entire text of OpenShot.POT
f = open(os.path.join(language_folder_path, "OpenShot", "OpenShot.pot"), "r")
# read entire text of file
entire_source = f.read()
f.close()

# Create Final POT Output File
if os.path.exists(os.path.join(language_folder_path, "OpenShot", "OpenShot.pot")):
    os.remove(os.path.join(language_folder_path, "OpenShot", "OpenShot.pot"))
final = open(os.path.join(language_folder_path, "OpenShot", "OpenShot.pot"), "w")
final.write(header_text)
final.write("\n")

# Move transitions POT file to final location
if os.path.exists(os.path.join(language_folder_path, "OpenShot_transitions.pot")):
    os.rename(os.path.join(language_folder_path, "OpenShot_transitions.pot"),
              os.path.join(language_folder_path, "OpenShot", "OpenShot_transitions.pot"))

# Move emoji POT file to final location
if os.path.exists(os.path.join(language_folder_path, "OpenShot_emojis.pot")):
    os.rename(os.path.join(language_folder_path, "OpenShot_emojis.pot"),
              os.path.join(language_folder_path, "OpenShot", "OpenShot_emojis.pot"))

# Move blender POT file to final location
if os.path.exists(os.path.join(language_folder_path, "OpenShot_blender.pot")):
    os.rename(os.path.join(language_folder_path, "OpenShot_blender.pot"),
              os.path.join(language_folder_path, "OpenShot", "OpenShot_blender.pot"))

# Trim the beginning off of each POT file
start_pos = entire_source.find("#: ")
trimmed_source = entire_source[start_pos:]

# Add to Final POT File
final.write(trimmed_source)
final.write("\n")

# Close final POT file
final.close()

log.info("-----------------------------------------------------")
log.info(" Remove all temp POT files ")
log.info("-----------------------------------------------------")

# Delete all 4 temp files
temp_files = ['OpenShot_source.pot', 'OpenShot_glade.pot', 'OpenShot_effects.pot', 'OpenShot_export.pot',
              'OpenShot_transitions.pot', 'OpenShot_QtUi.pot', 'OpenShot_QtUi.ts']
for temp_file_name in temp_files:
    temp_file_path = os.path.join(language_folder_path, temp_file_name)
    if os.path.exists(temp_file_path):
        os.remove(temp_file_path)

# output success
log.info("-----------------------------------------------------")
log.info(" The OpenShot.pot file has been successfully created ")
log.info(" with all text in OpenShot.")
log.info("")
log.info(" Checking for duplicate keys...")
log.info("-----------------------------------------------------")

# Find any duplicate translations between our 4 template files
# If these duplicates are translated differently, we will end up
# with conflicts, and both translations will be combined incorrectly
all_strings = {}

for pot_file in [
    'OpenShot.pot',
    'OpenShot_transitions.pot',
    'OpenShot_blender.pot',
    'OpenShot_emojis.pot',
]:
    with open(os.path.join(language_folder_path, "OpenShot", pot_file)) as f:
        data = f.read()
        for key in re.findall('^msgid \"(.*)\"', data, re.MULTILINE):
            if key not in all_strings:
                all_strings[key] = "%s | %s" % (key, pot_file)
            elif key and key not in ('translator-credits', ):
                log.info(' ERROR: Duplicate key found: %s::%s' % (pot_file, all_strings[key]))