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