Merge branch 'blender-v2.92-release'
[blender-addons.git] / ui_translate / update_svn.py
blob0c362d1efaf118e98c016064423099528a5b994d
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8 compliant>
21 if "bpy" in locals():
22 import importlib
23 importlib.reload(settings)
24 importlib.reload(utils_i18n)
25 importlib.reload(utils_languages_menu)
26 else:
27 import bpy
28 from bpy.types import Operator
29 from bpy.props import (
30 BoolProperty,
31 EnumProperty,
33 from . import settings
34 from bl_i18n_utils import utils as utils_i18n
35 from bl_i18n_utils import utils_languages_menu
37 import concurrent.futures
38 import io
39 import os
40 import shutil
41 import subprocess
42 import tempfile
45 # Operators ###################################################################
47 def i18n_updatetranslation_svn_branches_callback(pot, lng, settings):
48 if not lng['use']:
49 return
50 if os.path.isfile(lng['po_path']):
51 po = utils_i18n.I18nMessages(uid=lng['uid'], kind='PO', src=lng['po_path'], settings=settings)
52 po.update(pot)
53 else:
54 po = pot
55 po.write(kind="PO", dest=lng['po_path'])
56 print("{} PO written!".format(lng['uid']))
59 class UI_OT_i18n_updatetranslation_svn_branches(Operator):
60 """Update i18n svn's branches (po files)"""
61 bl_idname = "ui.i18n_updatetranslation_svn_branches"
62 bl_label = "Update I18n Branches"
64 use_skip_pot_gen: BoolProperty(
65 name="Skip POT",
66 description="Skip POT file generation",
67 default=False,
70 def execute(self, context):
71 if not hasattr(self, "settings"):
72 self.settings = settings.settings
73 i18n_sett = context.window_manager.i18n_update_svn_settings
74 self.settings.FILE_NAME_POT = i18n_sett.pot_path
76 context.window_manager.progress_begin(0, len(i18n_sett.langs) + 1)
77 context.window_manager.progress_update(0)
78 if not self.use_skip_pot_gen:
79 env = os.environ.copy()
80 env["ASAN_OPTIONS"] = "exitcode=0:" + os.environ.get("ASAN_OPTIONS", "")
81 # Generate base pot from RNA messages (we use another blender instance here, to be able to perfectly
82 # control our environment (factory startup, specific addons enabled/disabled...)).
83 # However, we need to export current user settings about this addon!
84 cmmd = (
85 bpy.app.binary_path,
86 "--background",
87 "--factory-startup",
88 "--python",
89 os.path.join(os.path.dirname(utils_i18n.__file__), "bl_extract_messages.py"),
90 "--",
91 "--settings",
92 self.settings.to_json(),
94 # Not working (UI is not refreshed...).
95 #self.report({'INFO'}, "Extracting messages, this will take some time...")
96 context.window_manager.progress_update(1)
97 ret = subprocess.run(cmmd, env=env)
98 if ret.returncode != 0:
99 self.report({'ERROR'}, "Message extraction process failed!")
100 context.window_manager.progress_end()
101 return {'CANCELLED'}
103 # Now we should have a valid POT file, we have to merge it in all languages po's...
104 with concurrent.futures.ProcessPoolExecutor() as exctr:
105 pot = utils_i18n.I18nMessages(kind='PO', src=self.settings.FILE_NAME_POT, settings=self.settings)
106 num_langs = len(i18n_sett.langs)
107 for progress, _ in enumerate(exctr.map(i18n_updatetranslation_svn_branches_callback,
108 (pot,) * num_langs,
109 [dict(lng.items()) for lng in i18n_sett.langs],
110 (self.settings,) * num_langs,
111 chunksize=4)):
112 context.window_manager.progress_update(progress + 2)
113 context.window_manager.progress_end()
114 return {'FINISHED'}
116 def invoke(self, context, event):
117 wm = context.window_manager
118 return wm.invoke_props_dialog(self)
121 def i18n_cleanuptranslation_svn_branches_callback(lng, settings):
122 if not lng['use']:
123 print("Skipping {} language ({}).".format(lng['name'], lng['uid']))
124 return
125 po = utils_i18n.I18nMessages(uid=lng['uid'], kind='PO', src=lng['po_path'], settings=settings)
126 errs = po.check(fix=True)
127 cleanedup_commented = po.clean_commented()
128 po.write(kind="PO", dest=lng['po_path'])
129 print("Processing {} language ({}).\n"
130 "Cleaned up {} commented messages.\n".format(lng['name'], lng['uid'], cleanedup_commented) +
131 ("Errors in this po, solved as best as possible!\n\t" + "\n\t".join(errs) if errs else "") + "\n")
134 class UI_OT_i18n_cleanuptranslation_svn_branches(Operator):
135 """Clean up i18n svn's branches (po files)"""
136 bl_idname = "ui.i18n_cleanuptranslation_svn_branches"
137 bl_label = "Clean up I18n Branches"
139 def execute(self, context):
140 if not hasattr(self, "settings"):
141 self.settings = settings.settings
142 i18n_sett = context.window_manager.i18n_update_svn_settings
143 # 'DEFAULT' and en_US are always valid, fully-translated "languages"!
144 stats = {"DEFAULT": 1.0, "en_US": 1.0}
146 context.window_manager.progress_begin(0, len(i18n_sett.langs) + 1)
147 context.window_manager.progress_update(0)
148 with concurrent.futures.ProcessPoolExecutor() as exctr:
149 num_langs = len(i18n_sett.langs)
150 for progress, _ in enumerate(exctr.map(i18n_cleanuptranslation_svn_branches_callback,
151 [dict(lng.items()) for lng in i18n_sett.langs],
152 (self.settings,) * num_langs,
153 chunksize=4)):
154 context.window_manager.progress_update(progress + 1)
156 context.window_manager.progress_end()
158 return {'FINISHED'}
161 def i18n_updatetranslation_svn_trunk_callback(lng, settings):
162 if lng['uid'] in settings.IMPORT_LANGUAGES_SKIP:
163 print("Skipping {} language ({}), edit settings if you want to enable it.\n".format(lng['name'], lng['uid']))
164 return lng['uid'], 0.0
165 if not lng['use']:
166 print("Skipping {} language ({}).\n".format(lng['name'], lng['uid']))
167 return lng['uid'], 0.0
168 po = utils_i18n.I18nMessages(uid=lng['uid'], kind='PO', src=lng['po_path'], settings=settings)
169 errs = po.check(fix=True)
170 print("Processing {} language ({}).\n"
171 "Cleaned up {} commented messages.\n".format(lng['name'], lng['uid'], po.clean_commented()) +
172 ("Errors in this po, solved as best as possible!\n\t" + "\n\t".join(errs) if errs else "") + "\n")
173 if lng['uid'] in settings.IMPORT_LANGUAGES_RTL:
174 po.write(kind="PO", dest=lng['po_path_trunk'][:-3] + "_raw.po")
175 po.rtl_process()
176 po.write(kind="PO", dest=lng['po_path_trunk'])
177 po.write(kind="PO_COMPACT", dest=lng['po_path_git'])
178 po.write(kind="MO", dest=lng['mo_path_trunk'])
179 po.update_info()
180 return lng['uid'], po.nbr_trans_msgs / po.nbr_msgs
183 class UI_OT_i18n_updatetranslation_svn_trunk(Operator):
184 """Update i18n svn's branches (po files)"""
185 bl_idname = "ui.i18n_updatetranslation_svn_trunk"
186 bl_label = "Update I18n Trunk"
188 def execute(self, context):
189 if not hasattr(self, "settings"):
190 self.settings = settings.settings
191 i18n_sett = context.window_manager.i18n_update_svn_settings
192 # 'DEFAULT' and en_US are always valid, fully-translated "languages"!
193 stats = {"DEFAULT": 1.0, "en_US": 1.0}
195 context.window_manager.progress_begin(0, len(i18n_sett.langs) + 1)
196 context.window_manager.progress_update(0)
197 with concurrent.futures.ProcessPoolExecutor() as exctr:
198 num_langs = len(i18n_sett.langs)
199 for progress, (lng_uid, stats_val) in enumerate(exctr.map(i18n_updatetranslation_svn_trunk_callback,
200 [dict(lng.items()) for lng in i18n_sett.langs],
201 (self.settings,) * num_langs,
202 chunksize=4)):
203 context.window_manager.progress_update(progress + 1)
204 stats[lng_uid] = stats_val
206 # Copy pot file from branches to trunk.
207 shutil.copy2(self.settings.FILE_NAME_POT, self.settings.TRUNK_PO_DIR)
209 print("Generating languages' menu...")
210 context.window_manager.progress_update(progress + 2)
211 # First complete our statistics by checking po files we did not touch this time!
212 po_to_uid = {os.path.basename(lng.po_path): lng.uid for lng in i18n_sett.langs}
213 for po_path in os.listdir(self.settings.TRUNK_PO_DIR):
214 uid = po_to_uid.get(po_path, None)
215 po_path = os.path.join(self.settings.TRUNK_PO_DIR, po_path)
216 if uid and uid not in stats:
217 po = utils_i18n.I18nMessages(uid=uid, kind='PO', src=po_path, settings=self.settings)
218 stats[uid] = po.nbr_trans_msgs / po.nbr_msgs if po.nbr_msgs > 0 else 0
219 utils_languages_menu.gen_menu_file(stats, self.settings)
220 context.window_manager.progress_end()
222 return {'FINISHED'}
225 class UI_OT_i18n_updatetranslation_svn_statistics(Operator):
226 """Create or extend a 'i18n_info.txt' Text datablock"""
227 """(it will contain statistics and checks about current branches and/or trunk)"""
228 bl_idname = "ui.i18n_updatetranslation_svn_statistics"
229 bl_label = "Update I18n Statistics"
231 use_branches: BoolProperty(
232 name="Check Branches",
233 description="Check po files in branches",
234 default=True,
237 use_trunk: BoolProperty(
238 name="Check Trunk",
239 description="Check po files in trunk",
240 default=False,
243 report_name = "i18n_info.txt"
245 def execute(self, context):
246 if not hasattr(self, "settings"):
247 self.settings = settings.settings
248 i18n_sett = context.window_manager.i18n_update_svn_settings
250 buff = io.StringIO()
251 lst = []
252 if self.use_branches:
253 lst += [(lng, lng.po_path) for lng in i18n_sett.langs]
254 if self.use_trunk:
255 lst += [(lng, lng.po_path_trunk) for lng in i18n_sett.langs
256 if lng.uid not in self.settings.IMPORT_LANGUAGES_SKIP]
258 context.window_manager.progress_begin(0, len(lst))
259 context.window_manager.progress_update(0)
260 for progress, (lng, path) in enumerate(lst):
261 context.window_manager.progress_update(progress + 1)
262 if not lng.use:
263 print("Skipping {} language ({}).".format(lng.name, lng.uid))
264 continue
265 buff.write("Processing {} language ({}, {}).\n".format(lng.name, lng.uid, path))
266 po = utils_i18n.I18nMessages(uid=lng.uid, kind='PO', src=path, settings=self.settings)
267 po.print_info(prefix=" ", output=buff.write)
268 errs = po.check(fix=False)
269 if errs:
270 buff.write(" WARNING! Po contains following errors:\n")
271 buff.write(" " + "\n ".join(errs))
272 buff.write("\n")
273 buff.write("\n\n")
275 text = None
276 if self.report_name not in bpy.data.texts:
277 text = bpy.data.texts.new(self.report_name)
278 else:
279 text = bpy.data.texts[self.report_name]
280 data = text.as_string()
281 data = data + "\n" + buff.getvalue()
282 text.from_string(data)
283 self.report({'INFO'}, "Info written to {} text datablock!".format(self.report_name))
284 context.window_manager.progress_end()
286 return {'FINISHED'}
288 def invoke(self, context, event):
289 wm = context.window_manager
290 return wm.invoke_props_dialog(self)
293 classes = (
294 UI_OT_i18n_updatetranslation_svn_branches,
295 UI_OT_i18n_cleanuptranslation_svn_branches,
296 UI_OT_i18n_updatetranslation_svn_trunk,
297 UI_OT_i18n_updatetranslation_svn_statistics,