OpenShot Video Editor  2.0.0
generate_translations.py
Go to the documentation of this file.
1 ##
2 #
3 # @file
4 # @brief This file updates the OpenShot.POT (language translation template) by scanning all source files.
5 # @author Jonathan Thomas <jonathan@openshot.org>
6 #
7 # This file helps you generate the POT file that contains all of the translatable
8 # strings / text in OpenShot. Because some of our text is in custom XML files,
9 # the xgettext command can't correctly generate the POT file. Thus... the
10 # existance of this file. =)
11 #
12 # Command to create the individual language PO files (Ascii files)
13 # $ msginit --input=OpenShot.pot --locale=fr_FR
14 # $ msginit --input=OpenShot.pot --locale=es
15 #
16 # Command to update the PO files (if text is added or changed)
17 # $ msgmerge en_US.po OpenShot.pot -U
18 # $ msgmerge es.po OpenShot.pot -U
19 #
20 # Command to compile the Ascii PO files into binary MO files
21 # $ msgfmt en_US.po --output-file=en_US/LC_MESSAGES/OpenShot.mo
22 # $ msgfmt es.po --output-file=es/LC_MESSAGES/OpenShot.mo
23 #
24 # Command to compile all PO files in a folder
25 # $ find -iname "*.po" -exec msgfmt {} -o {}.mo \;
26 #
27 # Command to combine the 2 pot files into 1 file
28 # $ msgcat ~/openshot/locale/OpenShot/OpenShot_source.pot ~/openshot/openshot/locale/OpenShot/OpenShot_glade.pot -o ~/openshot/main/locale/OpenShot/OpenShot.pot
29 #
30 # @section LICENSE
31 #
32 # Copyright (c) 2008-2016 OpenShot Studios, LLC
33 # (http://www.openshotstudios.com). This file is part of
34 # OpenShot Video Editor (http://www.openshot.org), an open-source project
35 # dedicated to delivering high quality video editing and animation solutions
36 # to the world.
37 #
38 # OpenShot Video Editor is free software: you can redistribute it and/or modify
39 # it under the terms of the GNU General Public License as published by
40 # the Free Software Foundation, either version 3 of the License, or
41 # (at your option) any later version.
42 #
43 # OpenShot Video Editor is distributed in the hope that it will be useful,
44 # but WITHOUT ANY WARRANTY; without even the implied warranty of
45 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
46 # GNU General Public License for more details.
47 #
48 # You should have received a copy of the GNU General Public License
49 # along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
50 #
51 
52 import shutil
53 import datetime
54 import os
55 import subprocess
56 import sys
57 import xml.dom.minidom as xml
58 import json
59 import openshot
60 
61 # Get the absolute path of this project
62 path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
63 if path not in sys.path:
64  sys.path.append(path)
65 
66 import classes.info as info
67 from classes.logger import log
68 
69 # get the path of the main OpenShot folder
70 langage_folder_path = os.path.dirname(os.path.abspath(__file__))
71 openshot_path = os.path.dirname(langage_folder_path)
72 effects_path = os.path.join(openshot_path, 'effects')
73 blender_path = os.path.join(openshot_path, 'blender')
74 transitions_path = os.path.join(openshot_path, 'transitions')
75 export_path = os.path.join(openshot_path, 'presets')
76 windows_ui_path = os.path.join(openshot_path, 'windows', 'ui')
77 locale_path = os.path.join(openshot_path, 'locale', 'OpenShot')
78 
79 log.info("-----------------------------------------------------")
80 log.info(" Creating temp POT files")
81 log.info("-----------------------------------------------------")
82 
83 # create empty temp files in the /openshot/language folder (these are used as temp POT files)
84 temp_files = ['OpenShot_source.pot', 'OpenShot_glade.pot', 'OpenShot_effects.pot', 'OpenShot_export.pot',
85  'OpenShot_transitions.pot', 'OpenShot_QtUi.pot']
86 for temp_file_name in temp_files:
87  temp_file_path = os.path.join(langage_folder_path, temp_file_name)
88  if os.path.exists(temp_file_path):
89  os.remove(temp_file_path)
90  f = open(temp_file_path, "w")
91  f.close()
92 
93 log.info("-----------------------------------------------------")
94 log.info(" Using xgettext to generate .py POT files")
95 log.info("-----------------------------------------------------")
96 
97 # Generate POT for Source Code strings (i.e. strings marked with a _("translate me"))
98 subprocess.call('find %s -iname "*.py" -exec xgettext -j -o %s --keyword=_ {} \;' % (
99 openshot_path, os.path.join(langage_folder_path, 'OpenShot_source.pot')), shell=True)
100 
101 log.info("-----------------------------------------------------")
102 log.info(" Using Qt's lupdate to generate .ui POT files")
103 log.info("-----------------------------------------------------")
104 
105 # Generate POT for Qt *.ui files (which require the lupdate command, and ts2po command)
106 os.chdir(windows_ui_path)
107 subprocess.call('lupdate *.ui -ts %s' % (os.path.join(langage_folder_path, 'OpenShot_QtUi.ts')), shell=True)
108 subprocess.call('lupdate *.ui -ts %s' % (os.path.join(langage_folder_path, 'OpenShot_QtUi.pot')), shell=True)
109 os.chdir(langage_folder_path)
110 
111 # Rewrite the UI POT, removing msgctxt
112 output = open(os.path.join(langage_folder_path, "clean.po"), 'w')
113 for line in open(os.path.join(langage_folder_path, 'OpenShot_QtUi.pot'), 'r'):
114  if not line.startswith('msgctxt'):
115  output.write(line)
116 # Overwrite original PO file
117 output.close()
118 shutil.copy(os.path.join(langage_folder_path, "clean.po"), os.path.join(langage_folder_path, 'OpenShot_QtUi.pot'))
119 os.remove(os.path.join(langage_folder_path, "clean.po"))
120 
121 # Remove duplciates (if any found)
122 subprocess.call('msguniq %s --use-first -o %s' % (os.path.join(langage_folder_path, 'OpenShot_QtUi.pot'),
123  os.path.join(langage_folder_path, 'clean.po')), shell=True)
124 shutil.copy(os.path.join(langage_folder_path, "clean.po"), os.path.join(langage_folder_path, 'OpenShot_QtUi.pot'))
125 os.remove(os.path.join(langage_folder_path, "clean.po"))
126 
127 
128 log.info("-----------------------------------------------------")
129 log.info(" Updating auto created POT files to set CharSet")
130 log.info("-----------------------------------------------------")
131 
132 temp_files = ['OpenShot_source.pot', 'OpenShot_glade.pot']
133 for temp_file in temp_files:
134  # get the entire text
135  f = open(os.path.join(langage_folder_path, temp_file), "r")
136  # read entire text of file
137  entire_source = f.read()
138  f.close()
139 
140  # replace charset
141  entire_source = entire_source.replace("charset=CHARSET", "charset=UTF-8")
142 
143  # Create Updated POT Output File
144  if os.path.exists(os.path.join(langage_folder_path, temp_file)):
145  os.remove(os.path.join(langage_folder_path, temp_file))
146  f = open(os.path.join(langage_folder_path, temp_file), "w")
147  f.write(entire_source)
148  f.close()
149 
150 log.info("-----------------------------------------------------")
151 log.info(" Scanning custom XML files and finding text")
152 log.info("-----------------------------------------------------")
153 
154 # Loop through the Effects XML
155 effects_text = {}
156 for file in os.listdir(effects_path):
157  if os.path.isfile(os.path.join(effects_path, file)):
158  # load xml effect file
159  full_file_path = os.path.join(effects_path, file)
160  xmldoc = xml.parse(os.path.join(effects_path, file))
161 
162  # add text to list
163  effects_text[xmldoc.getElementsByTagName("title")[0].childNodes[0].data] = full_file_path
164  effects_text[xmldoc.getElementsByTagName("description")[0].childNodes[0].data] = full_file_path
165 
166  # get params
167  params = xmldoc.getElementsByTagName("param")
168 
169  # Loop through params
170  for param in params:
171  if param.attributes["title"]:
172  effects_text[param.attributes["title"].value] = full_file_path
173 
174 # Append on properties from libopenshot
175 objects = [openshot.Clip(), openshot.Blur(), openshot.Brightness(),
176  openshot.ChromaKey(), openshot.Deinterlace(), openshot.Mask(),
177  openshot.Negate(), openshot.Saturation()]
178 
179 # Loop through each libopenshot object
180 for object in objects:
181  props = json.loads(object.PropertiesJSON(1))
182 
183  # Loop through props
184  for key in props.keys():
185  object = props[key]
186  if "name" in object.keys():
187  effects_text[object["name"]] = "libopenshot (Clip Properties)"
188  if "choices" in object.keys():
189  for choice in object["choices"]:
190  effects_text[choice["name"]] = "libopenshot (Clip Properties)"
191 
192 # Append Effect Meta Data
193 e = openshot.EffectInfo()
194 props = json.loads(e.Json())
195 
196 # Loop through props
197 for effect in props:
198  if "name" in effect:
199  effects_text[effect["name"]] = "libopenshot (Effect Metadata)"
200  if "description" in effect:
201  effects_text[effect["description"]] = "libopenshot (Effect Metadata)"
202 
203 # Loop through the Blender XML
204 for file in os.listdir(blender_path):
205  if os.path.isfile(os.path.join(blender_path, file)):
206  # load xml effect file
207  full_file_path = os.path.join(blender_path, file)
208  xmldoc = xml.parse(os.path.join(blender_path, file))
209 
210  # add text to list
211  effects_text[xmldoc.getElementsByTagName("title")[0].childNodes[0].data] = full_file_path
212 
213  # get params
214  params = xmldoc.getElementsByTagName("param")
215 
216  # Loop through params
217  for param in params:
218  if param.attributes["title"]:
219  effects_text[param.attributes["title"].value] = full_file_path
220 
221 # Loop through the Export Settings XML
222 export_text = {}
223 for file in os.listdir(export_path):
224  if os.path.isfile(os.path.join(export_path, file)):
225  # load xml export file
226  full_file_path = os.path.join(export_path, file)
227  xmldoc = xml.parse(os.path.join(export_path, file))
228 
229  # add text to list
230  export_text[xmldoc.getElementsByTagName("type")[0].childNodes[0].data] = full_file_path
231  export_text[xmldoc.getElementsByTagName("title")[0].childNodes[0].data] = full_file_path
232 
233 # Loop through Settings
234 settings_file = open(os.path.join(info.PATH, 'settings', '_default.settings'), 'r').read()
235 settings = json.loads(settings_file)
236 category_names = []
237 for setting in settings:
238  if "type" in setting and setting["type"] != "hidden":
239  # Add visible settings
240  export_text[setting["title"]] = "Settings for %s" % setting["setting"]
241  if "type" in setting and setting["type"] != "hidden":
242  # Add visible category names
243  if setting["category"] not in category_names:
244  export_text[setting["category"]] = "Settings Category for %s" % setting["category"]
245  category_names.append(setting["category"])
246 
247 # Loop through transitions and add to POT file
248 transitions_text = {}
249 for file in os.listdir(transitions_path):
250  # load xml export file
251  full_file_path = os.path.join(transitions_path, file)
252  (fileBaseName, fileExtension) = os.path.splitext(file)
253 
254  # get transition name
255  name = fileBaseName.replace("_", " ").capitalize()
256 
257  # add text to list
258  transitions_text[name] = full_file_path
259 
260  # Look in sub-folders
261  for sub_file in os.listdir(full_file_path):
262  # load xml export file
263  full_subfile_path = os.path.join(full_file_path, sub_file)
264  (fileBaseName, fileExtension) = os.path.splitext(sub_file)
265 
266  # split the name into parts (looking for a number)
267  suffix_number = None
268  name_parts = fileBaseName.split("_")
269  if name_parts[-1].isdigit():
270  suffix_number = name_parts[-1]
271 
272  # get transition name
273  name = fileBaseName.replace("_", " ").capitalize()
274 
275  # replace suffix number with placeholder (if any)
276  if suffix_number:
277  name = name.replace(suffix_number, "%s")
278 
279  # add text to list
280  transitions_text[name] = full_subfile_path
281 
282 
283 log.info("-----------------------------------------------------")
284 log.info(" Creating the custom XML POT files")
285 log.info("-----------------------------------------------------")
286 
287 # header of POT file
288 header_text = ""
289 header_text = header_text + '# OpenShot Video Editor POT Template File.\n'
290 header_text = header_text + '# Copyright (C) 2008-2016 OpenShot Studios, LLC\n'
291 header_text = header_text + '# This file is distributed under the same license as OpenShot.\n'
292 header_text = header_text + '# Jonathan Thomas <Jonathan.Oomph@gmail.com>, 2016.\n'
293 header_text = header_text + '#\n'
294 header_text = header_text + '#, fuzzy\n'
295 header_text = header_text + 'msgid ""\n'
296 header_text = header_text + 'msgstr ""\n'
297 header_text = header_text + '"Project-Id-Version: OpenShot Video Editor (version: %s)\\n"\n' % info.VERSION
298 header_text = header_text + '"Report-Msgid-Bugs-To: Jonathan Thomas <Jonathan.Oomph@gmail.com>\\n"\n'
299 header_text = header_text + '"POT-Creation-Date: %s\\n"\n' % datetime.datetime.now()
300 header_text = header_text + '"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"\n'
301 header_text = header_text + '"Last-Translator: Jonathan Thomas <Jonathan.Oomph@gmail.com>\\n"\n'
302 header_text = header_text + '"Language-Team: https://translations.launchpad.net/+groups/launchpad-translators\\n"\n'
303 header_text = header_text + '"MIME-Version: 1.0\\n"\n'
304 header_text = header_text + '"Content-Type: text/plain; charset=UTF-8\\n"\n'
305 header_text = header_text + '"Content-Transfer-Encoding: 8bit\\n"\n'
306 
307 # Create POT files for the custom text (from our XML files)
308 temp_files = [['OpenShot_effects.pot', effects_text], ['OpenShot_export.pot', export_text],
309  ['OpenShot_transitions.pot', transitions_text]]
310 for temp_file, text_dict in temp_files:
311  f = open(temp_file, "w")
312 
313  # write header
314  f.write(header_text)
315 
316  # loop through each line of text
317  for k, v in text_dict.items():
318  if k:
319  f.write('\n')
320  f.write('#: %s\n' % v)
321  f.write('msgid "%s"\n' % k)
322  f.write('msgstr ""\n')
323 
324  # close file
325  f.close()
326 
327 log.info("-----------------------------------------------------")
328 log.info(" Combine all temp POT files using msgcat command (this removes dupes) ")
329 log.info("-----------------------------------------------------")
330 
331 temp_files = ['OpenShot_source.pot', 'OpenShot_glade.pot', 'OpenShot_effects.pot', 'OpenShot_export.pot',
332  'OpenShot_transitions.pot', 'OpenShot_QtUi.pot']
333 command = "msgcat"
334 for temp_file in temp_files:
335  # append files
336  command = command + " " + os.path.join(langage_folder_path, temp_file)
337 command = command + " -o " + os.path.join(locale_path, "OpenShot.pot")
338 
339 log.info(command)
340 
341 # merge all 4 temp POT files
342 subprocess.call(command, shell=True)
343 
344 log.info("-----------------------------------------------------")
345 log.info(" Create FINAL POT File from all temp POT files ")
346 log.info("-----------------------------------------------------")
347 
348 # get the entire text of OpenShot.POT
349 f = open(os.path.join(locale_path, "OpenShot.pot"), "r")
350 # read entire text of file
351 entire_source = f.read()
352 f.close()
353 
354 # Create Final POT Output File
355 if os.path.exists(os.path.join(locale_path, "OpenShot.pot")):
356  os.remove(os.path.join(locale_path, "OpenShot.pot"))
357 final = open(os.path.join(locale_path, "OpenShot.pot"), "w")
358 final.write(header_text)
359 final.write("\n")
360 
361 # Trim the beginning off of each POT file
362 start_pos = entire_source.find("#: ")
363 trimmed_source = entire_source[start_pos:]
364 
365 # Add to Final POT File
366 final.write(trimmed_source)
367 final.write("\n")
368 
369 # Close final POT file
370 final.close()
371 
372 log.info("-----------------------------------------------------")
373 log.info(" Remove all temp POT files ")
374 log.info("-----------------------------------------------------")
375 
376 # Delete all 4 temp files
377 temp_files = ['OpenShot_source.pot', 'OpenShot_glade.pot', 'OpenShot_effects.pot', 'OpenShot_export.pot',
378  'OpenShot_transitions.pot', 'OpenShot_QtUi.pot', 'OpenShot_QtUi.ts']
379 for temp_file_name in temp_files:
380  temp_file_path = os.path.join(langage_folder_path, temp_file_name)
381  if os.path.exists(temp_file_path):
382  os.remove(temp_file_path)
383 
384 # output success
385 log.info("-----------------------------------------------------")
386 log.info(" The /openshot/locale/OpenShot/OpenShot.pot file has")
387 log.info(" been successfully created with all text in OpenShot.")
388 log.info("-----------------------------------------------------")