Merge branch 'blender-v2.92-release'
[blender-addons.git] / ui_translate / edit_translation.py
blob1aeee13af7990051c1b6b3c0ccc9892c15858943
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 import os
22 import shutil
23 if "bpy" in locals():
24 import importlib
25 importlib.reload(settings)
26 importlib.reload(utils_i18n)
27 else:
28 import bpy
29 from bpy.types import Operator
30 from bpy.props import (
31 BoolProperty,
32 EnumProperty,
33 StringProperty,
35 from . import settings
36 from bl_i18n_utils import utils as utils_i18n
39 # A global cache for I18nMessages objects, as parsing po files takes a few seconds.
40 PO_CACHE = {}
43 def _get_messages(lang, fname):
44 if fname not in PO_CACHE:
45 PO_CACHE[fname] = utils_i18n.I18nMessages(uid=lang, kind='PO', key=fname, src=fname, settings=settings.settings)
46 return PO_CACHE[fname]
49 class UI_OT_i18n_edittranslation_update_mo(Operator):
50 """Try to "compile" given po file into relevant blender.mo file"""
51 """(WARNING: it will replace the official mo file in your user dir!)"""
52 bl_idname = "ui.i18n_edittranslation_update_mo"
53 bl_label = "Edit Translation Update Mo"
55 # Operator Arguments
56 lang: StringProperty(
57 description="Current (translated) language",
58 options={'SKIP_SAVE'},
61 po_file: StringProperty(
62 description="Path to the matching po file",
63 subtype='FILE_PATH',
64 options={'SKIP_SAVE'},
67 clean_mo: BoolProperty(
68 description="Remove all local translation files, to be able to use the system ones again",
69 default=False,
70 options={'SKIP_SAVE'}
72 # /End Operator Arguments
74 def execute(self, context):
75 if self.clean_mo:
76 root = bpy.utils.user_resource('DATAFILES', settings.settings.MO_PATH_ROOT_RELATIVE)
77 if root:
78 shutil.rmtree(root)
79 elif not (self.lang and self.po_file):
80 return {'CANCELLED'}
81 else:
82 mo_dir = bpy.utils.user_resource('DATAFILES', settings.settings.MO_PATH_TEMPLATE_RELATIVE.format(self.lang),
83 create=True)
84 mo_file = os.path.join(mo_dir, settings.settings.MO_FILE_NAME)
85 _get_messages(self.lang, self.po_file).write(kind='MO', dest=mo_file)
87 bpy.ops.ui.reloadtranslation()
88 return {'FINISHED'}
91 class UI_OT_i18n_edittranslation(Operator):
92 """Translate the label and tooltip of the given property"""
93 bl_idname = "ui.edittranslation"
94 bl_label = "Edit Translation"
96 # Operator Arguments
97 but_label: StringProperty(
98 description="Label of the control",
99 options={'SKIP_SAVE'},
102 rna_label: StringProperty(
103 description="RNA-defined label of the control, if any",
104 options={'SKIP_SAVE'},
107 enum_label: StringProperty(
108 description="Label of the enum item of the control, if any",
109 options={'SKIP_SAVE'},
112 but_tip: StringProperty(
113 description="Tip of the control",
114 options={'SKIP_SAVE'},
117 rna_tip: StringProperty(
118 description="RNA-defined tip of the control, if any",
119 options={'SKIP_SAVE'},
122 enum_tip: StringProperty(
123 description="Tip of the enum item of the control, if any",
124 options={'SKIP_SAVE'},
127 rna_struct: StringProperty(
128 description="Identifier of the RNA struct, if any",
129 options={'SKIP_SAVE'},
132 rna_prop: StringProperty(
133 description="Identifier of the RNA property, if any",
134 options={'SKIP_SAVE'},
137 rna_enum: StringProperty(
138 description="Identifier of the RNA enum item, if any",
139 options={'SKIP_SAVE'},
142 rna_ctxt: StringProperty(
143 description="RNA context for label",
144 options={'SKIP_SAVE'},
147 lang: StringProperty(
148 description="Current (translated) language",
149 options={'SKIP_SAVE'},
152 po_file: StringProperty(
153 description="Path to the matching po file",
154 subtype='FILE_PATH',
155 options={'SKIP_SAVE'},
158 # Found in po file.
159 org_but_label: StringProperty(
160 description="Original label of the control",
161 options={'SKIP_SAVE'},
164 org_rna_label: StringProperty(
165 description="Original RNA-defined label of the control, if any",
166 options={'SKIP_SAVE'},
169 org_enum_label: StringProperty(
170 description="Original label of the enum item of the control, if any",
171 options={'SKIP_SAVE'},
174 org_but_tip: StringProperty(
175 description="Original tip of the control",
176 options={'SKIP_SAVE'},
179 org_rna_tip: StringProperty(
180 description="Original RNA-defined tip of the control, if any", options={'SKIP_SAVE'}
183 org_enum_tip: StringProperty(
184 description="Original tip of the enum item of the control, if any",
185 options={'SKIP_SAVE'},
188 flag_items = (
189 ('FUZZY', "Fuzzy", "Message is marked as fuzzy in po file"),
190 ('ERROR', "Error", "Some error occurred with this message"),
193 but_label_flags: EnumProperty(
194 description="Flags about the label of the button",
195 items=flag_items,
196 options={'SKIP_SAVE', 'ENUM_FLAG'},
199 rna_label_flags: EnumProperty(
200 description="Flags about the RNA-defined label of the button",
201 items=flag_items,
202 options={'SKIP_SAVE', 'ENUM_FLAG'},
205 enum_label_flags: EnumProperty(
206 description="Flags about the RNA enum item label of the button",
207 items=flag_items,
208 options={'SKIP_SAVE', 'ENUM_FLAG'},
211 but_tip_flags: EnumProperty(
212 description="Flags about the tip of the button",
213 items=flag_items,
214 options={'SKIP_SAVE', 'ENUM_FLAG'},
217 rna_tip_flags: EnumProperty(
218 description="Flags about the RNA-defined tip of the button",
219 items=flag_items,
220 options={'SKIP_SAVE', 'ENUM_FLAG'},
223 enum_tip_flags: EnumProperty(
224 description="Flags about the RNA enum item tip of the button",
225 items=flag_items,
226 options={'SKIP_SAVE', 'ENUM_FLAG'},
229 stats_str: StringProperty(
230 description="Stats from opened po", options={'SKIP_SAVE'})
232 update_po: BoolProperty(
233 description="Update po file, try to rebuild mo file, and refresh Blender's UI",
234 default=False,
235 options={'SKIP_SAVE'},
238 update_mo: BoolProperty(
239 description="Try to rebuild mo file, and refresh Blender's UI",
240 default=False,
241 options={'SKIP_SAVE'},
244 clean_mo: BoolProperty(
245 description="Remove all local translation files, to be able to use the system ones again",
246 default=False,
247 options={'SKIP_SAVE'},
249 # /End Operator Arguments
251 def execute(self, context):
252 if not hasattr(self, "msgmap"):
253 self.report('ERROR', "invoke() needs to be called before execute()")
254 return {'CANCELLED'}
256 msgs = _get_messages(self.lang, self.po_file)
257 done_keys = set()
258 for mmap in self.msgmap.values():
259 if 'ERROR' in getattr(self, mmap["msg_flags"]):
260 continue
261 k = mmap["key"]
262 if k not in done_keys and len(k) == 1:
263 k = tuple(k)[0]
264 msgs.msgs[k].msgstr = getattr(self, mmap["msgstr"])
265 msgs.msgs[k].is_fuzzy = 'FUZZY' in getattr(self, mmap["msg_flags"])
266 done_keys.add(k)
268 if self.update_po:
269 # Try to overwrite .po file, may fail if there are no permissions.
270 try:
271 msgs.write(kind='PO', dest=self.po_file)
272 except Exception as e:
273 self.report('ERROR', "Could not write to po file ({})".format(str(e)))
274 # Always invalidate reverse messages cache afterward!
275 msgs.invalidate_reverse_cache()
276 if self.update_mo:
277 lang = os.path.splitext(os.path.basename(self.po_file))[0]
278 bpy.ops.ui.i18n_edittranslation_update_mo(po_file=self.po_file, lang=lang)
279 elif self.clean_mo:
280 bpy.ops.ui.i18n_edittranslation_update_mo(clean_mo=True)
281 return {'FINISHED'}
283 def invoke(self, context, event):
284 self.msgmap = {
285 "but_label": {
286 "msgstr": "but_label", "msgid": "org_but_label", "msg_flags": "but_label_flags", "key": set()},
287 "rna_label": {
288 "msgstr": "rna_label", "msgid": "org_rna_label", "msg_flags": "rna_label_flags", "key": set()},
289 "enum_label": {
290 "msgstr": "enum_label", "msgid": "org_enum_label", "msg_flags": "enum_label_flags", "key": set()},
291 "but_tip": {
292 "msgstr": "but_tip", "msgid": "org_but_tip", "msg_flags": "but_tip_flags", "key": set()},
293 "rna_tip": {
294 "msgstr": "rna_tip", "msgid": "org_rna_tip", "msg_flags": "rna_tip_flags", "key": set()},
295 "enum_tip": {
296 "msgstr": "enum_tip", "msgid": "org_enum_tip", "msg_flags": "enum_tip_flags", "key": set()},
299 msgs = _get_messages(self.lang, self.po_file)
300 msgs.find_best_messages_matches(self, self.msgmap, self.rna_ctxt, self.rna_struct, self.rna_prop, self.rna_enum)
301 msgs.update_info()
302 self.stats_str = "{}: {} messages, {} translated.".format(os.path.basename(self.po_file), msgs.nbr_msgs,
303 msgs.nbr_trans_msgs)
305 for mmap in self.msgmap.values():
306 k = tuple(mmap["key"])
307 if k:
308 if len(k) == 1:
309 k = k[0]
310 ctxt, msgid = k
311 setattr(self, mmap["msgstr"], msgs.msgs[k].msgstr)
312 setattr(self, mmap["msgid"], msgid)
313 if msgs.msgs[k].is_fuzzy:
314 setattr(self, mmap["msg_flags"], {'FUZZY'})
315 else:
316 setattr(self, mmap["msgid"],
317 "ERROR: Button label “{}” matches several messages in po file ({})!"
318 "".format(self.but_label, k))
319 setattr(self, mmap["msg_flags"], {'ERROR'})
320 else:
321 setattr(self, mmap["msgstr"], "")
322 setattr(self, mmap["msgid"], "")
324 wm = context.window_manager
325 return wm.invoke_props_dialog(self, width=600)
327 def draw(self, context):
328 layout = self.layout
329 layout.label(text=self.stats_str)
330 src, _a, _b = bpy.utils.make_rna_paths(self.rna_struct, self.rna_prop, self.rna_enum)
331 if src:
332 layout.label(text=" RNA Path: bpy.types." + src)
333 if self.rna_ctxt:
334 layout.label(text=" RNA Context: " + self.rna_ctxt)
336 if self.org_but_label or self.org_rna_label or self.org_enum_label:
337 # XXX Can't use box, labels are not enough readable in them :/
338 box = layout.box()
339 box.label(text="Labels:")
340 split = box.split(factor=0.15)
341 col1 = split.column()
342 col2 = split.column()
343 if self.org_but_label:
344 col1.label(text="Button Label:")
345 row = col2.row()
346 row.enabled = False
347 if 'ERROR' in self.but_label_flags:
348 row.alert = True
349 else:
350 col1.prop_enum(self, "but_label_flags", 'FUZZY', text="Fuzzy")
351 col2.prop(self, "but_label", text="")
352 row.prop(self, "org_but_label", text="")
353 if self.org_rna_label:
354 col1.label(text="RNA Label:")
355 row = col2.row()
356 row.enabled = False
357 if 'ERROR' in self.rna_label_flags:
358 row.alert = True
359 else:
360 col1.prop_enum(self, "rna_label_flags", 'FUZZY', text="Fuzzy")
361 col2.prop(self, "rna_label", text="")
362 row.prop(self, "org_rna_label", text="")
363 if self.org_enum_label:
364 col1.label(text="Enum Item Label:")
365 row = col2.row()
366 row.enabled = False
367 if 'ERROR' in self.enum_label_flags:
368 row.alert = True
369 else:
370 col1.prop_enum(self, "enum_label_flags", 'FUZZY', text="Fuzzy")
371 col2.prop(self, "enum_label", text="")
372 row.prop(self, "org_enum_label", text="")
374 if self.org_but_tip or self.org_rna_tip or self.org_enum_tip:
375 # XXX Can't use box, labels are not enough readable in them :/
376 box = layout.box()
377 box.label(text="Tool Tips:")
378 split = box.split(factor=0.15)
379 col1 = split.column()
380 col2 = split.column()
381 if self.org_but_tip:
382 col1.label(text="Button Tip:")
383 row = col2.row()
384 row.enabled = False
385 if 'ERROR' in self.but_tip_flags:
386 row.alert = True
387 else:
388 col1.prop_enum(self, "but_tip_flags", 'FUZZY', text="Fuzzy")
389 col2.prop(self, "but_tip", text="")
390 row.prop(self, "org_but_tip", text="")
391 if self.org_rna_tip:
392 col1.label(text="RNA Tip:")
393 row = col2.row()
394 row.enabled = False
395 if 'ERROR' in self.rna_tip_flags:
396 row.alert = True
397 else:
398 col1.prop_enum(self, "rna_tip_flags", 'FUZZY', text="Fuzzy")
399 col2.prop(self, "rna_tip", text="")
400 row.prop(self, "org_rna_tip", text="")
401 if self.org_enum_tip:
402 col1.label(text="Enum Item Tip:")
403 row = col2.row()
404 row.enabled = False
405 if 'ERROR' in self.enum_tip_flags:
406 row.alert = True
407 else:
408 col1.prop_enum(self, "enum_tip_flags", 'FUZZY', text="Fuzzy")
409 col2.prop(self, "enum_tip", text="")
410 row.prop(self, "org_enum_tip", text="")
412 row = layout.row()
413 row.prop(self, "update_po", text="Save to PO File", toggle=True)
414 row.prop(self, "update_mo", text="Rebuild MO File", toggle=True)
415 row.prop(self, "clean_mo", text="Erase Local MO files", toggle=True)
418 classes = (
419 UI_OT_i18n_edittranslation_update_mo,
420 UI_OT_i18n_edittranslation,