Skip to content
Snippets Groups Projects
Commit b39c8c26 authored by Bastien Montagne's avatar Bastien Montagne
Browse files

Big i18n tools update, II/II.

Now everything should be done with ui_translate addon (which is also now fully functional again, in theory ;) ).

Notes:
* Everything is still a bit raw and sometimes hackish.
* Not every feature implemented yet.
* A bunch of cleanup is still needed.
* Doc needs to be updated too!
parent 5b6e54f7
No related branches found
No related tags found
No related merge requests found
...@@ -19,308 +19,47 @@ ...@@ -19,308 +19,47 @@
# <pep8 compliant> # <pep8 compliant>
bl_info = { bl_info = {
"name": "Translate UI Messages", "name": "Manage UI translations",
"author": "Bastien Montagne", "author": "Bastien Montagne",
"blender": (2, 63, 0), "blender": (2, 65, 10),
"location": "Any UI control", "location": "Main \"File\" menu, text editor, any UI control",
"description": "Allow to translate UI directly from Blender", "description": "Allow to manage UI translations directly from Blender (update main po files, "
"warning": "Broken in this release!", "update scripts' translations, etc.)",
"wiki_url": "", "warning": "Still in development, not all features are fully implemented yet!",
"tracker_url": "", "wiki_url": "http://wiki.blender.org/index.php/Dev:Doc/How_to/Translate_Blender/Addon",
"tracker_url": "http://projects.blender.org/tracker/?atid=498&group_id=9&func=browse",
"support": 'OFFICIAL', "support": 'OFFICIAL',
"category": "System"} "category": "System"}
if "bpy" in locals(): if "bpy" in locals():
import imp import imp
if "ui_utils" in locals(): imp.reload(settings)
imp.reload(ui_utils) imp.reload(edit_translation)
imp.reload(update_svn)
else: else:
import bpy import bpy
from bpy.props import (BoolProperty, from . import settings
CollectionProperty, from . import edit_translation
EnumProperty, from . import update_svn
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from . import utils as ui_utils
from bl_i18n_utils import utils as i18n_utils
from bl_i18n_utils import update_mo
#from bl_i18n_utils import settings
import os import os
import shutil
# module-level cache, as parsing po files takes a few seconds...
# Keys are po file paths, data are the results of i18n_utils.parse_messages().
PO_CACHE = {}
def clear_caches(key):
del PO_CACHE[key]
del ui_utils.WORK_CACHE[key]
class UI_OT_edittranslation_update_mo(bpy.types.Operator):
"""Try to "compile" given po file into relevant blender.mo file """ \
"""(WARNING: it will replace the official mo file in your user dir!)"""
bl_idname = "ui.edittranslation_update_mo"
bl_label = "Edit Translation Update Mo"
# "Parameters"
lang = StringProperty(description="Current (translated) language",
options={'SKIP_SAVE'})
po_file = StringProperty(description="Path to the matching po file",
subtype='FILE_PATH', options={'SKIP_SAVE'})
clean_mo = BoolProperty(description="Clean up (remove) all local "
"translation files, to be able to use "
"all system's ones again",
default=False, options={'SKIP_SAVE'})
def execute(self, context):
if self.clean_mo:
root = bpy.utils.user_resource('DATAFILES', ui_utils.MO_PATH_ROOT)
if root:
shutil.rmtree(root)
elif not self.lang or not self.po_file:
return {'CANCELLED'}
else:
mo_dir = bpy.utils.user_resource(
'DATAFILES', ui_utils.MO_PATH_TEMPLATE.format(self.lang),
create=True)
mo_file = os.path.join(mo_dir, ui_utils.MO_FILENAME)
update_mo.process_po(self.po_file, None, mo_file)
bpy.ops.ui.reloadtranslation()
return {'FINISHED'}
class UI_OT_edittranslation(bpy.types.Operator):
"""Translate the label and tool tip of the property defined by given 'parameters'"""
bl_idname = "ui.edittranslation"
bl_label = "Edit Translation"
# "Parameters"
but_label = StringProperty(description="Label of the control", options={'SKIP_SAVE'})
rna_label = StringProperty(description="RNA-defined label of the control, if any", options={'SKIP_SAVE'})
enum_label = StringProperty(description="Label of the enum item of the control, if any", options={'SKIP_SAVE'})
but_tip = StringProperty(description="Tip of the control", options={'SKIP_SAVE'})
rna_tip = StringProperty(description="RNA-defined tip of the control, if any", options={'SKIP_SAVE'})
enum_tip = StringProperty(description="Tip of the enum item of the control, if any", options={'SKIP_SAVE'})
rna_struct = StringProperty(description="Identifier of the RNA struct, if any", options={'SKIP_SAVE'})
rna_prop = StringProperty(description="Identifier of the RNA property, if any", options={'SKIP_SAVE'})
rna_enum = StringProperty(description="Identifier of the RNA enum item, if any", options={'SKIP_SAVE'})
rna_ctxt = StringProperty(description="RNA context for label", options={'SKIP_SAVE'})
lang = StringProperty(description="Current (translated) language", options={'SKIP_SAVE'})
po_file = StringProperty(description="Path to the matching po file", subtype='FILE_PATH', options={'SKIP_SAVE'})
# Found in po file.
org_but_label = StringProperty(description="Original label of the control", options={'SKIP_SAVE'})
org_rna_label = StringProperty(description="Original RNA-defined label of the control, if any", options={'SKIP_SAVE'})
org_enum_label = StringProperty(description="Original label of the enum item of the control, if any", options={'SKIP_SAVE'})
org_but_tip = StringProperty(description="Original tip of the control", options={'SKIP_SAVE'})
org_rna_tip = StringProperty(description="Original RNA-defined tip of the control, if any", options={'SKIP_SAVE'})
org_enum_tip = StringProperty(description="Original tip of the enum item of the control, if any", options={'SKIP_SAVE'})
flag_items = (('FUZZY', "Fuzzy", "Message is marked as fuzzy in po file"),
('ERROR', "Error", "Some error occurred with this message"),
)
but_label_flags = EnumProperty(items=flag_items, description="Flags about the label of the button", options={'SKIP_SAVE', 'ENUM_FLAG'})
rna_label_flags = EnumProperty(items=flag_items, description="Flags about the RNA-defined label of the button", options={'SKIP_SAVE', 'ENUM_FLAG'})
enum_label_flags = EnumProperty(items=flag_items, description="Flags about the RNA enum item label of the button", options={'SKIP_SAVE', 'ENUM_FLAG'})
but_tip_flags = EnumProperty(items=flag_items, description="Flags about the tip of the button", options={'SKIP_SAVE', 'ENUM_FLAG'})
rna_tip_flags = EnumProperty(items=flag_items, description="Flags about the RNA-defined tip of the button", options={'SKIP_SAVE', 'ENUM_FLAG'})
enum_tip_flags = EnumProperty(items=flag_items, description="Flags about the RNA enum item tip of the button", options={'SKIP_SAVE', 'ENUM_FLAG'})
stats_str = StringProperty(description="Stats from opened po", options={'SKIP_SAVE'})
update_po = BoolProperty(description="Update po file, try to rebuild mo file, and refresh Blender UI", default=False, options={'SKIP_SAVE'})
update_mo = BoolProperty(description="Try to rebuild mo file, and refresh Blender UI (WARNING: you should use a local Blender installation, as you probably have no right to write in the system Blender installation...)", default=False, options={'SKIP_SAVE'})
clean_mo = BoolProperty(description="Clean up (remove) all local "
"translation files, to be able to use "
"all system's ones again",
default=False, options={'SKIP_SAVE'})
def execute(self, context):
if not hasattr(self, "msgmap"):
# We must be invoked() first!
return {'CANCELLED'}
msgs, state, stats = PO_CACHE[self.po_file]
done_keys = set()
for mmap in self.msgmap.values():
if 'ERROR' in getattr(self, mmap["msg_flags"]):
continue
k = mmap["key"]
# print(k)
if k not in done_keys and len(k) == 1:
k = tuple(k)[0]
msgs[k]["msgstr_lines"] = [getattr(self, mmap["msgstr"])]
if k in state["fuzzy_msg"] and 'FUZZY' not in getattr(self, mmap["msg_flags"]):
state["fuzzy_msg"].remove(k)
elif k not in state["fuzzy_msg"] and 'FUZZY' in getattr(self, mmap["msg_flags"]):
state["fuzzy_msg"].add(k)
done_keys.add(k)
if self.update_po:
# Try to overwrite po file, may fail if we have no good rights...
try:
i18n_utils.write_messages(self.po_file, msgs, state["comm_msg"], state["fuzzy_msg"])
except Exception as e:
self.report('ERROR', "Could not write to po file ({})".format(str(e)))
# Always invalidate all caches afterward!
clear_caches(self.po_file)
if self.update_mo:
lang = os.path.splitext(os.path.basename(self.po_file))[0]
bpy.ops.ui.edittranslation_update_mo(po_file=self.po_file, lang=lang)
elif self.clean_mo:
bpy.ops.ui.edittranslation_update_mo(clean_mo=True)
return {'FINISHED'}
def invoke(self, context, event):
if self.po_file in PO_CACHE:
msgs, state, stats = PO_CACHE[self.po_file]
else:
msgs, state, stats = PO_CACHE.setdefault(self.po_file, i18n_utils.parse_messages(self.po_file))
self.msgmap = {"but_label": {"msgstr": "but_label", "msgid": "org_but_label", "msg_flags": "but_label_flags", "key": set()},
"rna_label": {"msgstr": "rna_label", "msgid": "org_rna_label", "msg_flags": "rna_label_flags", "key": set()},
"enum_label": {"msgstr": "enum_label", "msgid": "org_enum_label", "msg_flags": "enum_label_flags", "key": set()},
"but_tip": {"msgstr": "but_tip", "msgid": "org_but_tip", "msg_flags": "but_tip_flags", "key": set()},
"rna_tip": {"msgstr": "rna_tip", "msgid": "org_rna_tip", "msg_flags": "rna_tip_flags", "key": set()},
"enum_tip": {"msgstr": "enum_tip", "msgid": "org_enum_tip", "msg_flags": "enum_tip_flags", "key": set()},
}
ui_utils.find_best_msgs_matches(self, self.po_file, self.msgmap, msgs, state, self.rna_ctxt,
self.rna_struct, self.rna_prop, self.rna_enum)
self.stats_str = "{}: {} messages, {} translated.".format(os.path.basename(self.po_file), stats["tot_msg"], stats["trans_msg"])
for mmap in self.msgmap.values():
k = tuple(mmap["key"])
if k:
if len(k) == 1:
k = k[0]
ctxt, msgid = k
setattr(self, mmap["msgstr"], "".join(msgs[k]["msgstr_lines"]))
setattr(self, mmap["msgid"], msgid)
if k in state["fuzzy_msg"]:
setattr(self, mmap["msg_flags"], {'FUZZY'})
else:
setattr(self, mmap["msgid"], "ERROR: Button label “{}” matches none or several messages in po file ({})!".format(self.but_label, k))
setattr(self, mmap["msg_flags"], {'ERROR'})
else:
setattr(self, mmap["msgstr"], "")
setattr(self, mmap["msgid"], "")
wm = context.window_manager
return wm.invoke_props_dialog(self, width=600)
def draw(self, context):
layout = self.layout
layout.label(text=self.stats_str)
src, _a, _b = ui_utils.bpy_path(self.rna_struct, self.rna_prop, self.rna_enum)
if src:
layout.label(text=" RNA Path: bpy.types." + src)
if self.rna_ctxt:
layout.label(text=" RNA Context: " + self.rna_ctxt)
if self.org_but_label or self.org_rna_label or self.org_enum_label:
# XXX Can't use box, labels are not enought readable in them :/
# box = layout.box()
box = layout
box.label(text="Labels:")
split = box.split(percentage=0.15)
col1 = split.column()
col2 = split.column()
if self.org_but_label:
col1.label(text="Button Label:")
row = col2.row()
row.enabled = False
if 'ERROR' in self.but_label_flags:
row.alert = True
else:
col1.prop_enum(self, "but_label_flags", 'FUZZY', text="Fuzzy")
col2.prop(self, "but_label", text="")
row.prop(self, "org_but_label", text="")
if self.org_rna_label:
col1.label(text="RNA Label:")
row = col2.row()
row.enabled = False
if 'ERROR' in self.rna_label_flags:
row.alert = True
else:
col1.prop_enum(self, "rna_label_flags", 'FUZZY', text="Fuzzy")
col2.prop(self, "rna_label", text="")
row.prop(self, "org_rna_label", text="")
if self.org_enum_label:
col1.label(text="Enum Item Label:")
row = col2.row()
row.enabled = False
if 'ERROR' in self.enum_label_flags:
row.alert = True
else:
col1.prop_enum(self, "enum_label_flags", 'FUZZY', text="Fuzzy")
col2.prop(self, "enum_label", text="")
row.prop(self, "org_enum_label", text="")
if self.org_but_tip or self.org_rna_tip or self.org_enum_tip:
# XXX Can't use box, labels are not enought readable in them :/
# box = layout.box()
box = layout
box.label(text="Tool Tips:")
split = box.split(percentage=0.15)
col1 = split.column()
col2 = split.column()
if self.org_but_tip:
col1.label(text="Button Tip:")
row = col2.row()
row.enabled = False
if 'ERROR' in self.but_tip_flags:
row.alert = True
else:
col1.prop_enum(self, "but_tip_flags", 'FUZZY', text="Fuzzy")
col2.prop(self, "but_tip", text="")
row.prop(self, "org_but_tip", text="")
if self.org_rna_tip:
col1.label(text="RNA Tip:")
row = col2.row()
row.enabled = False
if 'ERROR' in self.rna_tip_flags:
row.alert = True
else:
col1.prop_enum(self, "rna_tip_flags", 'FUZZY', text="Fuzzy")
col2.prop(self, "rna_tip", text="")
row.prop(self, "org_rna_tip", text="")
if self.org_enum_tip:
col1.label(text="Enum Item Tip:")
row = col2.row()
row.enabled = False
if 'ERROR' in self.enum_tip_flags:
row.alert = True
else:
col1.prop_enum(self, "enum_tip_flags", 'FUZZY', text="Fuzzy")
col2.prop(self, "enum_tip", text="")
row.prop(self, "org_enum_tip", text="")
row = layout.row()
row.prop(self, "update_po", text="Save to PO File", toggle=True)
row.prop(self, "update_mo", text="Rebuild MO File", toggle=True)
row.prop(self, "clean_mo", text="Erase Local MO files", toggle=True)
def register(): def register():
bpy.utils.register_module(__name__) bpy.utils.register_module(__name__)
bpy.types.WindowManager.i18n_update_svn_settings = \
bpy.props.PointerProperty(type=update_svn.I18nUpdateTranslationSettings)
# Init addon's preferences (unfortunately, as we are using an external storage for the properties,
# the load/save user preferences process has no effect on them :( ).
if __name__ in bpy.context.user_preferences.addons:
pref = bpy.context.user_preferences.addons[__name__].preferences
if os.path.isfile(pref.persistent_data_path):
pref._settings.load(pref.persistent_data_path, reset=True)
def unregister(): def unregister():
del bpy.types.WindowManager.i18n_update_svn_settings
bpy.utils.unregister_module(__name__) bpy.utils.unregister_module(__name__)
if __name__ == "__main__":
register()
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program 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 2
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
if "bpy" in locals():
import imp
imp.reload(settings)
imp.reload(i18n_utils)
else:
import bpy
from bpy.props import (BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from . import settings
from bl_i18n_utils import utils as i18n_utils
import os
import shutil
# A global cache for I18nMessages objects, as parsing po files takes a few seconds.
PO_CACHE = {}
def _get_messages(lang, fname):
if fname not in PO_CACHE:
PO_CACHE[fname] = i18n_utils.I18nMessages(uid=lang, kind='PO', key=fname, src=fname, settings=settings.settings)
return PO_CACHE[fname]
class UI_OT_i18n_edittranslation_update_mo(bpy.types.Operator):
"""Try to "compile" given po file into relevant blender.mo file """ \
"""(WARNING: it will replace the official mo file in your user dir!)"""
bl_idname = "ui.i18n_edittranslation_update_mo"
bl_label = "Edit Translation Update Mo"
# "Parameters"
lang = StringProperty(description="Current (translated) language",
options={'SKIP_SAVE'})
po_file = StringProperty(description="Path to the matching po file",
subtype='FILE_PATH', options={'SKIP_SAVE'})
clean_mo = BoolProperty(description="Clean up (remove) all local "
"translation files, to be able to use "
"all system's ones again",
default=False, options={'SKIP_SAVE'})
def execute(self, context):
if self.clean_mo:
root = bpy.utils.user_resource('DATAFILES', settings.settings.MO_PATH_ROOT_RELATIVE)
if root:
shutil.rmtree(root)
elif not (self.lang and self.po_file):
return {'CANCELLED'}
else:
mo_dir = bpy.utils.user_resource('DATAFILES', settings.settings.MO_PATH_TEMPLATE_RELATIVE.format(self.lang),
create=True)
mo_file = os.path.join(mo_dir, settings.settings.MO_FILE_NAME)
_get_messages(self.lang, self.po_file).write(kind='MO', dest=mo_file)
bpy.ops.ui.reloadtranslation()
return {'FINISHED'}
class UI_OT_i18n_edittranslation(bpy.types.Operator):
"""Translate the label and tool tip of the property defined by given 'parameters'"""
bl_idname = "ui.edittranslation"
bl_label = "Edit Translation"
# "Parameters"
but_label = StringProperty(description="Label of the control", options={'SKIP_SAVE'})
rna_label = StringProperty(description="RNA-defined label of the control, if any", options={'SKIP_SAVE'})
enum_label = StringProperty(description="Label of the enum item of the control, if any", options={'SKIP_SAVE'})
but_tip = StringProperty(description="Tip of the control", options={'SKIP_SAVE'})
rna_tip = StringProperty(description="RNA-defined tip of the control, if any", options={'SKIP_SAVE'})
enum_tip = StringProperty(description="Tip of the enum item of the control, if any", options={'SKIP_SAVE'})
rna_struct = StringProperty(description="Identifier of the RNA struct, if any", options={'SKIP_SAVE'})
rna_prop = StringProperty(description="Identifier of the RNA property, if any", options={'SKIP_SAVE'})
rna_enum = StringProperty(description="Identifier of the RNA enum item, if any", options={'SKIP_SAVE'})
rna_ctxt = StringProperty(description="RNA context for label", options={'SKIP_SAVE'})
lang = StringProperty(description="Current (translated) language", options={'SKIP_SAVE'})
po_file = StringProperty(description="Path to the matching po file", subtype='FILE_PATH', options={'SKIP_SAVE'})
# Found in po file.
org_but_label = StringProperty(description="Original label of the control", options={'SKIP_SAVE'})
org_rna_label = StringProperty(description="Original RNA-defined label of the control, if any",
options={'SKIP_SAVE'})
org_enum_label = StringProperty(description="Original label of the enum item of the control, if any",
options={'SKIP_SAVE'})
org_but_tip = StringProperty(description="Original tip of the control", options={'SKIP_SAVE'})
org_rna_tip = StringProperty(description="Original RNA-defined tip of the control, if any", options={'SKIP_SAVE'})
org_enum_tip = StringProperty(description="Original tip of the enum item of the control, if any",
options={'SKIP_SAVE'})
flag_items = (('FUZZY', "Fuzzy", "Message is marked as fuzzy in po file"),
('ERROR', "Error", "Some error occurred with this message"),
)
but_label_flags = EnumProperty(items=flag_items, description="Flags about the label of the button",
options={'SKIP_SAVE', 'ENUM_FLAG'})
rna_label_flags = EnumProperty(items=flag_items, description="Flags about the RNA-defined label of the button",
options={'SKIP_SAVE', 'ENUM_FLAG'})
enum_label_flags = EnumProperty(items=flag_items, description="Flags about the RNA enum item label of the button",
options={'SKIP_SAVE', 'ENUM_FLAG'})
but_tip_flags = EnumProperty(items=flag_items, description="Flags about the tip of the button",
options={'SKIP_SAVE', 'ENUM_FLAG'})
rna_tip_flags = EnumProperty(items=flag_items, description="Flags about the RNA-defined tip of the button",
options={'SKIP_SAVE', 'ENUM_FLAG'})
enum_tip_flags = EnumProperty(items=flag_items, description="Flags about the RNA enum item tip of the button",
options={'SKIP_SAVE', 'ENUM_FLAG'})
stats_str = StringProperty(description="Stats from opened po", options={'SKIP_SAVE'})
update_po = BoolProperty(description="Update po file, try to rebuild mo file, and refresh Blender UI",
default=False, options={'SKIP_SAVE'})
update_mo = BoolProperty(description="Try to rebuild mo file, and refresh Blender UI",
default=False, options={'SKIP_SAVE'})
clean_mo = BoolProperty(description="Clean up (remove) all local translation files, to be able to use "
"all system's ones again",
default=False, options={'SKIP_SAVE'})
def execute(self, context):
if not hasattr(self, "msgmap"):
self.report('ERROR', "Looks like you did not invoke this operator first!")
return {'CANCELLED'}
msgs = _get_messages(self.lang, self.po_file)
done_keys = set()
for mmap in self.msgmap.values():
if 'ERROR' in getattr(self, mmap["msg_flags"]):
continue
k = mmap["key"]
# print(k)
if k not in done_keys and len(k) == 1:
k = tuple(k)[0]
msgs.msgs[k].msgstr = getattr(self, mmap["msgstr"])
msgs.msgs[k].is_fuzzy = 'FUZZY' in getattr(self, mmap["msg_flags"])
done_keys.add(k)
if self.update_po:
# Try to overwrite po file, may fail if we have no good rights...
try:
msgs.write(kind='PO', dest=self.po_file)
except Exception as e:
self.report('ERROR', "Could not write to po file ({})".format(str(e)))
# Always invalidate reverse messages cache afterward!
msgs.invalidate_reverse_cache()
if self.update_mo:
lang = os.path.splitext(os.path.basename(self.po_file))[0]
bpy.ops.ui.i18n_edittranslation_update_mo(po_file=self.po_file, lang=lang)
elif self.clean_mo:
bpy.ops.ui.i18n_edittranslation_update_mo(clean_mo=True)
return {'FINISHED'}
def invoke(self, context, event):
self.msgmap = {"but_label": {"msgstr": "but_label", "msgid": "org_but_label",
"msg_flags": "but_label_flags", "key": set()},
"rna_label": {"msgstr": "rna_label", "msgid": "org_rna_label",
"msg_flags": "rna_label_flags", "key": set()},
"enum_label": {"msgstr": "enum_label", "msgid": "org_enum_label",
"msg_flags": "enum_label_flags", "key": set()},
"but_tip": {"msgstr": "but_tip", "msgid": "org_but_tip",
"msg_flags": "but_tip_flags", "key": set()},
"rna_tip": {"msgstr": "rna_tip", "msgid": "org_rna_tip",
"msg_flags": "rna_tip_flags", "key": set()},
"enum_tip": {"msgstr": "enum_tip", "msgid": "org_enum_tip",
"msg_flags": "enum_tip_flags", "key": set()},
}
msgs = _get_messages(self.lang, self.po_file)
msgs.find_best_messages_matches(self, self.msgmap, self.rna_ctxt, self.rna_struct, self.rna_prop, self.rna_enum)
msgs.update_info()
self.stats_str = "{}: {} messages, {} translated.".format(os.path.basename(self.po_file), msgs.nbr_msgs,
msgs.nbr_trans_msgs)
for mmap in self.msgmap.values():
k = tuple(mmap["key"])
if k:
if len(k) == 1:
k = k[0]
ctxt, msgid = k
setattr(self, mmap["msgstr"], msgs.msgs[k].msgstr)
setattr(self, mmap["msgid"], msgid)
if msgs.msgs[k].is_fuzzy:
setattr(self, mmap["msg_flags"], {'FUZZY'})
else:
setattr(self, mmap["msgid"],
"ERROR: Button label “{}” matches several messages in po file ({})!"
"".format(self.but_label, k))
setattr(self, mmap["msg_flags"], {'ERROR'})
else:
setattr(self, mmap["msgstr"], "")
setattr(self, mmap["msgid"], "")
wm = context.window_manager
return wm.invoke_props_dialog(self, width=600)
def draw(self, context):
layout = self.layout
layout.label(text=self.stats_str)
src, _a, _b = bpy.utils.make_rna_paths(self.rna_struct, self.rna_prop, self.rna_enum)
if src:
layout.label(text=" RNA Path: bpy.types." + src)
if self.rna_ctxt:
layout.label(text=" RNA Context: " + self.rna_ctxt)
if self.org_but_label or self.org_rna_label or self.org_enum_label:
# XXX Can't use box, labels are not enough readable in them :/
box = layout.box()
#box = layout
box.label(text="Labels:")
split = box.split(percentage=0.15)
col1 = split.column()
col2 = split.column()
if self.org_but_label:
col1.label(text="Button Label:")
row = col2.row()
row.enabled = False
if 'ERROR' in self.but_label_flags:
row.alert = True
else:
col1.prop_enum(self, "but_label_flags", 'FUZZY', text="Fuzzy")
col2.prop(self, "but_label", text="")
row.prop(self, "org_but_label", text="")
if self.org_rna_label:
col1.label(text="RNA Label:")
row = col2.row()
row.enabled = False
if 'ERROR' in self.rna_label_flags:
row.alert = True
else:
col1.prop_enum(self, "rna_label_flags", 'FUZZY', text="Fuzzy")
col2.prop(self, "rna_label", text="")
row.prop(self, "org_rna_label", text="")
if self.org_enum_label:
col1.label(text="Enum Item Label:")
row = col2.row()
row.enabled = False
if 'ERROR' in self.enum_label_flags:
row.alert = True
else:
col1.prop_enum(self, "enum_label_flags", 'FUZZY', text="Fuzzy")
col2.prop(self, "enum_label", text="")
row.prop(self, "org_enum_label", text="")
if self.org_but_tip or self.org_rna_tip or self.org_enum_tip:
# XXX Can't use box, labels are not enough readable in them :/
box = layout.box()
#box = layout
box.label(text="Tool Tips:")
split = box.split(percentage=0.15)
col1 = split.column()
col2 = split.column()
if self.org_but_tip:
col1.label(text="Button Tip:")
row = col2.row()
row.enabled = False
if 'ERROR' in self.but_tip_flags:
row.alert = True
else:
col1.prop_enum(self, "but_tip_flags", 'FUZZY', text="Fuzzy")
col2.prop(self, "but_tip", text="")
row.prop(self, "org_but_tip", text="")
if self.org_rna_tip:
col1.label(text="RNA Tip:")
row = col2.row()
row.enabled = False
if 'ERROR' in self.rna_tip_flags:
row.alert = True
else:
col1.prop_enum(self, "rna_tip_flags", 'FUZZY', text="Fuzzy")
col2.prop(self, "rna_tip", text="")
row.prop(self, "org_rna_tip", text="")
if self.org_enum_tip:
col1.label(text="Enum Item Tip:")
row = col2.row()
row.enabled = False
if 'ERROR' in self.enum_tip_flags:
row.alert = True
else:
col1.prop_enum(self, "enum_tip_flags", 'FUZZY', text="Fuzzy")
col2.prop(self, "enum_tip", text="")
row.prop(self, "org_enum_tip", text="")
row = layout.row()
row.prop(self, "update_po", text="Save to PO File", toggle=True)
row.prop(self, "update_mo", text="Rebuild MO File", toggle=True)
row.prop(self, "clean_mo", text="Erase Local MO files", toggle=True)
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program 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 2
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
if "bpy" in locals():
import imp
imp.reload(i18n_settings)
else:
import bpy
from bpy.props import (BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bl_i18n_utils import settings as i18n_settings
import os
settings = i18n_settings.I18nSettings()
class UI_OT_i18n_settings_load(bpy.types.Operator):
"""Load translations' settings from a persistent JSon file"""
bl_idname = "ui.i18n_settings_load"
bl_label = "I18n Load Settings"
bl_option = {'REGISTER'}
# "Parameters"
filepath = StringProperty(description="Path to the saved settings file",
subtype='FILE_PATH')
filter_glob = StringProperty(default="*.json", options={'HIDDEN'})
def invoke(self, context, event):
if not self.properties.is_property_set("filepath"):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
else:
return self.execute(context)
def execute(self, context):
if not (self.filepath and settings):
return {'CANCELLED'}
settings.load(self.filepath, reset=True)
return {'FINISHED'}
class UI_OT_i18n_settings_save(bpy.types.Operator):
"""Save translations' settings in a persistent JSon file"""
bl_idname = "ui.i18n_settings_save"
bl_label = "I18n Save Settings"
bl_option = {'REGISTER'}
# "Parameters"
filepath = StringProperty(description="Path to the saved settings file",
subtype='FILE_PATH')
filter_glob = StringProperty(default="*.json", options={'HIDDEN'})
def invoke(self, context, event):
if not self.properties.is_property_set("filepath"):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
else:
return self.execute(context)
def execute(self, context):
if not (self.filepath and settings):
return {'CANCELLED'}
settings.save(self.filepath)
return {'FINISHED'}
def _setattr(self, name, val):
print(self, name, val)
setattr(self, name, val)
class UI_AP_i18n_settings(bpy.types.AddonPreferences):
bl_idname = __name__.split(".")[0] # We want "top" module name!
bl_option = {'REGISTER'}
_settings = settings
WARN_MSGID_NOT_CAPITALIZED = BoolProperty(
name="Warn Msgid Not Capitalized",
description="Warn about messages not starting by a capitalized letter (with a few allowed exceptions!)",
default=True,
get=lambda self: self._settings.WARN_MSGID_NOT_CAPITALIZED,
set=lambda self, val: _setattr(self._settings, "WARN_MSGID_NOT_CAPITALIZED", val),
)
GETTEXT_MSGFMT_EXECUTABLE = StringProperty(
name="Gettext 'msgfmt' executable",
description="The gettext msgfmt 'compiler'. You’ll likely have to edit it if you’re under Windows",
subtype='FILE_PATH',
default="msgfmt",
get=lambda self: self._settings.GETTEXT_MSGFMT_EXECUTABLE,
set=lambda self, val: setattr(self._settings, "GETTEXT_MSGFMT_EXECUTABLE", val),
)
FRIBIDI_LIB = StringProperty(
name="Fribidi Library",
description="The FriBidi C compiled library (.so under Linux, .dll under windows...), you’ll likely have "
"to edit it if you’re under Windows, e.g. using the one included in svn's libraries repository",
subtype='FILE_PATH',
default="libfribidi.so.0",
get=lambda self: self._settings.FRIBIDI_LIB,
set=lambda self, val: setattr(self._settings, "FRIBIDI_LIB", val),
)
SOURCE_DIR = StringProperty(
name="Source Root",
description="The Blender source root path",
subtype='FILE_PATH',
default="blender",
get=lambda self: self._settings.SOURCE_DIR,
set=lambda self, val: setattr(self._settings, "SOURCE_DIR", val),
)
I18N_DIR = StringProperty(
name="Translation Root",
description="The bf-translation repository",
subtype='FILE_PATH',
default="i18n",
get=lambda self: self._settings.I18N_DIR,
set=lambda self, val: setattr(self._settings, "I18N_DIR", val),
)
SPELL_CACHE = StringProperty(
name="Spell Cache",
description="A cache storing validated msgids, to avoid re-spellchecking them",
subtype='FILE_PATH',
default=os.path.join("/tmp", ".spell_cache"),
get=lambda self: self._settings.SPELL_CACHE,
set=lambda self, val: setattr(self._settings, "SPELL_CACHE", val),
)
PY_SYS_PATHS = StringProperty(
name="Import Paths",
description="Additional paths to add to sys.path (';' separated)",
default="",
get=lambda self: self._settings.PY_SYS_PATHS,
set=lambda self, val: setattr(self._settings, "PY_SYS_PATHS", val),
)
persistent_data_path = StringProperty(
name="Persistent Data Path",
description="The name of a json file storing those settings (unfortunately, Blender's system "
"does not work here)",
subtype='FILE_PATH',
default=os.path.join("ui_translate_settings.json"),
)
_is_init = False
def draw(self, context):
layout = self.layout
layout.label(text="WARNING: preferences are lost when addon is disabled, be sure to use \"Save Persistent\" "
"if you want to keep your settings!")
layout.prop(self, "WARN_MSGID_NOT_CAPITALIZED")
layout.prop(self, "GETTEXT_MSGFMT_EXECUTABLE")
layout.prop(self, "FRIBIDI_LIB")
layout.prop(self, "SOURCE_DIR")
layout.prop(self, "I18N_DIR")
layout.prop(self, "SPELL_CACHE")
layout.prop(self, "PY_SYS_PATHS")
layout.separator()
split = layout.split(0.75)
col = split.column()
col.prop(self, "persistent_data_path")
row = col.row()
row.operator("UI_OT_i18n_settings_save", text="Save").filepath = self.persistent_data_path
row.operator("UI_OT_i18n_settings_load", text="Load").filepath = self.persistent_data_path
col = split.column()
col.operator("UI_OT_i18n_settings_save", text="Save Persistent To...")
col.operator("UI_OT_i18n_settings_load", text="Load Persistent From...")
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program 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 2
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
if "bpy" in locals():
import imp
imp.reload(settings)
imp.reload(i18n_utils)
imp.reload(languages_menu_utils)
else:
import bpy
from bpy.props import (BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from . import settings
from bl_i18n_utils import utils as i18n_utils
from bl_i18n_utils import languages_menu_utils
from bpy.app.translations import pgettext_iface as iface_
import os
import shutil
import subprocess
import tempfile
##### Helpers #####
def find_best_isocode_matches(uid, iso_codes):
tmp = ((e, i18n_utils.locale_match(e, uid)) for e in iso_codes)
return tuple(e[0] for e in sorted((e for e in tmp if e[1] is not ... and e[1] >= 0), key=lambda e: e[1]))
##### Data #####
class I18nUpdateTranslationLanguage(bpy.types.PropertyGroup):
"""Settings/info about a language"""
uid = StringProperty(name="Language ID", default="", description="Iso code, like fr_FR")
num_id = IntProperty(name="Numeric ID", default=0, min=0, description="Numeric ID (readonly!)")
name = StringProperty(name="Language Name", default="",
description="English language name/label (like \"French (Français)\")")
use = BoolProperty(name="Use", default=True, description="Use this language in current operator")
po_path = StringProperty(name="PO File Path", default="", subtype='FILE_PATH',
description="Path to the relevant po file in branches")
po_path_trunk = StringProperty(name="PO Trunk File Path", default="", subtype='FILE_PATH',
description="Path to the relevant po file in trunk")
mo_path_trunk = StringProperty(name="MO File Path", default="", subtype='FILE_PATH',
description="Path to the relevant mo file")
class I18nUpdateTranslationSettings(bpy.types.PropertyGroup):
"""Settings/info about a language"""
langs = CollectionProperty(name="Languages", type=I18nUpdateTranslationLanguage,
description="Languages to update in branches")
active_lang = IntProperty(name="Active Language", default=0,
description="Index of active language in langs collection")
pot_path = StringProperty(name="POT File Path", default="", subtype='FILE_PATH',
description="Path to the pot template file")
is_init = BoolProperty(default=False, options={'HIDDEN'},
description="Whether these settings have already been auto-set or not")
##### UI #####
class UI_UL_i18n_languages(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
#assert(isinstance(item, bpy.types.I18nUpdateTranslationLanguage))
if self.layout_type in {'DEFAULT', 'COMPACT'}:
layout.label(item.name, icon_value=icon)
layout.prop(item, "use", text="")
elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
layout.label(item.uid)
layout.prop(item, "use", text="")
class UI_PT_i18n_update_translations_settings(bpy.types.Panel):
bl_label = "I18n Update Translation Main"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "render"
def draw(self, context):
layout = self.layout
i18n_sett = context.window_manager.i18n_update_svn_settings
if not i18n_sett.is_init and bpy.ops.ui.i18n_updatetranslation_svn_init_settings.poll():
bpy.ops.ui.i18n_updatetranslation_svn_init_settings()
if not i18n_sett.is_init:
layout.label(text="Could not init languages data!")
layout.label(text="Please edit the preferences of the UI Translate addon")
else:
split = layout.split(0.75)
split.template_list("UI_UL_i18n_languages", "", i18n_sett, "langs", i18n_sett, "active_lang", rows=5)
col = split.column()
col.operator("ui.i18n_updatetranslation_svn_settings_select_all", text="Select All").use_select = True
col.operator("ui.i18n_updatetranslation_svn_settings_select_all", text="Deselect All").use_select = False
if i18n_sett.active_lang >= 0 and i18n_sett.active_lang < len(i18n_sett.langs):
lng = i18n_sett.langs[i18n_sett.active_lang]
col = layout.column()
col.active = lng.use
row = col.row()
row.label(text="[{}]: \"{}\" ({})".format(lng.uid, iface_(lng.name), lng.num_id), translate=False)
row.prop(lng, "use", text="")
col.prop(lng, "po_path")
col.prop(lng, "po_path_trunk")
col.prop(lng, "mo_path_trunk")
layout.separator()
layout.prop(i18n_sett, "pot_path")
row = layout.row()
row.operator("ui.i18n_updatetranslation_svn_init_settings", text="Reset Settings")
row.operator("ui.i18n_updatetranslation_svn_branches", text="Update Branches")
row.operator("ui.i18n_updatetranslation_svn_trunk", text="Update Trunk")
##### Operators #####
class UI_OT_i18n_updatetranslation_svn_init_settings(bpy.types.Operator):
"""Init settings for i18n svn's update operators"""
bl_idname = "ui.i18n_updatetranslation_svn_init_settings"
bl_label = "Init I18n Update Settings"
bl_option = {'REGISTER'}
@classmethod
def poll(cls, context):
return context.window_manager != None
def execute(self, context):
if not hasattr(self, "settings"):
self.settings = settings.settings
i18n_sett = context.window_manager.i18n_update_svn_settings
# First, create the list of languages from settings.
i18n_sett.langs.clear()
root_br = self.settings.BRANCHES_DIR
root_tr_po = self.settings.TRUNK_PO_DIR
root_tr_mo = os.path.join(self.settings.TRUNK_DIR, self.settings.MO_PATH_TEMPLATE, self.settings.MO_FILE_NAME)
if not (os.path.isdir(root_br) and os.path.isdir(root_tr_po)):
return {'CANCELLED'}
isocodes = ((e, os.path.join(root_br, e, e + ".po")) for e in os.listdir(root_br))
isocodes = dict(e for e in isocodes if os.path.isfile(e[1]))
for num_id, name, uid in self.settings.LANGUAGES[2:]: # Skip "default" and "en" languages!
best_po = find_best_isocode_matches(uid, isocodes)
#print(uid, "->", best_po)
lng = i18n_sett.langs.add()
lng.uid = uid
lng.num_id = num_id
lng.name = name
if best_po:
lng.use = True
isocode = best_po[0]
lng.po_path = isocodes[isocode]
lng.po_path_trunk = os.path.join(root_tr_po, isocode + ".po")
lng.mo_path_trunk = root_tr_mo.format(isocode)
else:
lng.use = False
language, _1, _2, language_country, language_variant = i18n_utils.locale_explode(uid)
for isocode in (language, language_variant, language_country, uid):
p = os.path.join(root_br, isocode, isocode + ".po")
if not os.path.exists(p):
lng.use = True
lng.po_path = p
lng.po_path_trunk = os.path.join(root_tr_po, isocode + ".po")
lng.mo_path_trunk = root_tr_mo.format(isocode)
break
i18n_sett.pot_path = self.settings.FILE_NAME_POT
i18n_sett.is_init = True
return {'FINISHED'}
class UI_OT_i18n_updatetranslation_svn_settings_select_all(bpy.types.Operator):
"""(De)select all languages for i18n svn's update operators"""
bl_idname = "ui.i18n_updatetranslation_svn_settings_select_all"
bl_label = "Init I18n Update Select Languages"
use_select = BoolProperty(default=True, description="Select all if True, else deselect all")
@classmethod
def poll(cls, context):
return context.window_manager != None
def execute(self, context):
for lng in context.window_manager.i18n_update_svn_settings.langs:
lng.use = self.use_select
return {'FINISHED'}
class UI_OT_i18n_updatetranslation_svn_branches(bpy.types.Operator):
"""Update i18n svn's branches (po files)"""
bl_idname = "ui.i18n_updatetranslation_svn_branches"
bl_label = "Update I18n Branches"
def execute(self, context):
if not hasattr(self, "settings"):
self.settings = settings.settings
i18n_sett = context.window_manager.i18n_update_svn_settings
self.settings.FILE_NAME_POT = i18n_sett.pot_path
# Generate base pot from RNA messages (we use another blender instance here, to be able to perfectly
# control our environment (factory startup, specific addons enabled/disabled...)).
# However, we need to export current user settings about this addon!
cmmd = (
bpy.app.binary_path,
"--background",
"--factory-startup",
"--python",
os.path.join(os.path.dirname(i18n_utils.__file__), "bl_extract_messages.py"),
"--",
"bl_extract_messages.py", # arg parser expects first arg to be prog name!
"--settings",
self.settings.to_json(),
)
if subprocess.call(cmmd):
self.report({'ERROR'}, "Message extraction process failed!")
return {'CANCELLED'}
# Now we should have a valid POT file, we have to merge it in all languages po's...
pot = i18n_utils.I18nMessages(kind='PO', src=self.settings.FILE_NAME_POT, settings=self.settings)
for lng in i18n_sett.langs:
if not lng.use:
continue
if os.path.isfile(lng.po_path):
po = i18n_utils.I18nMessages(uid=lng.uid, kind='PO', src=lng.po_path, settings=self.settings)
po.update(pot)
else:
po = pot
po.write(kind="PO", dest=lng.po_path)
print("{} PO written!".format(lng.uid))
return {'FINISHED'}
class UI_OT_i18n_updatetranslation_svn_trunk(bpy.types.Operator):
"""Update i18n svn's branches (po files)"""
bl_idname = "ui.i18n_updatetranslation_svn_trunk"
bl_label = "Update I18n Trunk"
def execute(self, context):
if not hasattr(self, "settings"):
self.settings = settings.settings
i18n_sett = context.window_manager.i18n_update_svn_settings
# 'DEFAULT' and en_US are always valid, fully-translated "languages"!
stats = {"DEFAULT": 1.0, "en_US": 1.0}
for lng in i18n_sett.langs:
if lng.uid in self.settings.IMPORT_LANGUAGES_SKIP:
print("Skipping {} language ({}), edit settings if you want to enable it.".format(lng.name, lng.uid))
continue
if not lng.use:
print("Skipping {} language ({}).".format(lng.name, lng.uid))
continue
print("Processing {} language ({}).".format(lng.name, lng.uid))
po = i18n_utils.I18nMessages(uid=lng.uid, kind='PO', src=lng.po_path, settings=self.settings)
print("Cleaned up {} commented messages.".format(po.clean_commented()))
errs = po.check(fix=True)
if errs:
print("Errors in this po, solved as best as possible!")
print("\t" + "\n\t".join(errs))
if lng.uid in self.settings.IMPORT_LANGUAGES_RTL:
po.write(kind="PO", dest=lng.po_path_trunk[:-3] + "_raw.po")
po.rtl_process()
po.write(kind="PO", dest=lng.po_path_trunk)
po.write(kind="MO", dest=lng.mo_path_trunk)
po.update_info()
stats[lng.uid] = po.nbr_trans_msgs / po.nbr_msgs
print("\n")
print("Generating languages' menu...")
# First complete our statistics by checking po files we did not touch this time!
po_to_uid = {os.path.basename(lng.po_path): lng.uid for lng in i18n_sett.langs}
for po_path in os.listdir(self.settings.TRUNK_PO_DIR):
uid = po_to_uid.get(po_path, None)
po_path = os.path.join(self.settings.TRUNK_PO_DIR, po_path)
if uid and uid not in stats:
po = i18n_utils.I18nMessages(uid=uid, kind='PO', src=po_path, settings=self.settings)
stats[uid] = po.nbr_trans_msgs / po.nbr_msgs
languages_menu_utils.gen_menu_file(stats, self.settings)
return {'FINISHED'}
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program 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 2
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
#from bl_i18n_utils import utils as i18n_utils
from bl_i18n_utils import settings
import os
# module-level cache, as parsing po files takes a few seconds...
# Keys are po file paths, data are the results of i18n_utils.parse_messages().
WORK_CACHE = {}
# Same as in BLF_translation.h
BLF_I18NCONTEXT_DEFAULT = ""
# Num buttons report their label with a trailing ': '...
NUM_BUTTON_SUFFIX = ": "
# Mo root datapath.
MO_PATH_ROOT = "locale"
# Mo path generator for a given language.
MO_PATH_TEMPLATE = os.path.join(MO_PATH_ROOT, "{}", "LC_MESSAGES")
# Mo filename.
MO_FILENAME = "blender.mo"
def bpy_path(rstruct, rprop, renum):
src = src_rna = src_enum = ""
if rstruct:
if rprop:
src = src_rna = ".".join((rstruct, rprop))
if renum:
src = src_enum = "{}.{}:'{}'".format(rstruct, rprop, renum)
else:
src = src_rna = rstruct
return src, src_rna, src_enum
def find_best_msgs_matches(obj, cache_key, msgmap, msgs, state, rna_ctxt, rstruct, rprop, renum):
comm_prfx = settings.COMMENT_PREFIX_SOURCE + "bpy.types."
# Build helper mappings.
# XXX We do not update this cache when editing a translation, as it would
# prevent the same msgid/msgstr to be find again.
# We only invalidate the cache once new po/mo have been generated!
if cache_key in WORK_CACHE:
src_to_msg, ctxt_to_msg, msgid_to_msg, msgstr_to_msg = WORK_CACHE[cache_key]
else:
src_to_msg = {}
ctxt_to_msg = {}
msgid_to_msg = {}
msgstr_to_msg = {}
for key, val in msgs.items():
ctxt, msgid = key
if key in state["comm_msg"]:
continue
ctxt_to_msg.setdefault(ctxt, set()).add(key)
msgid_to_msg.setdefault(msgid, set()).add(key)
msgstr_to_msg.setdefault("".join(val["msgstr_lines"]), set()).add(key)
for comm in val["comment_lines"]:
if comm.startswith(comm_prfx):
comm = comm[len(comm_prfx):]
src_to_msg.setdefault(comm, set()).add(key)
WORK_CACHE[cache_key] = (src_to_msg, ctxt_to_msg, msgid_to_msg, msgstr_to_msg)
# print(len(src_to_msg), len(ctxt_to_msg), len(msgid_to_msg), len(msgstr_to_msg))
# Build RNA key.
src, src_rna, src_enum = bpy_path(rstruct, rprop, renum)
print("src: ", src_rna, src_enum)
# Labels.
elbl = getattr(obj, msgmap["enum_label"]["msgstr"])
print("enum label: %r" % elbl)
if elbl:
# Enum items' labels have no i18n context...
k = ctxt_to_msg[BLF_I18NCONTEXT_DEFAULT].copy()
if elbl in msgid_to_msg:
k &= msgid_to_msg[elbl]
elif elbl in msgstr_to_msg:
k &= msgstr_to_msg[elbl]
else:
k = set()
# We assume if we already have only one key, it's the good one!
if len(k) > 1 and src_enum in src_to_msg:
k &= src_to_msg[src_enum]
msgmap["enum_label"]["key"] = k
rlbl = getattr(obj, msgmap["rna_label"]["msgstr"])
print("rna label: %r" % rlbl, rlbl in msgid_to_msg, rlbl in msgstr_to_msg)
if rlbl:
k = ctxt_to_msg[rna_ctxt].copy()
if k and rlbl in msgid_to_msg:
k &= msgid_to_msg[rlbl]
elif k and rlbl in msgstr_to_msg:
k &= msgstr_to_msg[rlbl]
else:
k = set()
# We assume if we already have only one key, it's the good one!
if len(k) > 1 and src_rna in src_to_msg:
k &= src_to_msg[src_rna]
msgmap["rna_label"]["key"] = k
blbl = getattr(obj, msgmap["but_label"]["msgstr"])
blbls = [blbl]
if blbl.endswith(NUM_BUTTON_SUFFIX):
# Num buttons report their label with a trailing ': '...
blbls.append(blbl[:-len(NUM_BUTTON_SUFFIX)])
print("button label: %r" % blbl)
if blbl and elbl not in blbls and (rlbl not in blbls or rna_ctxt != BLF_I18NCONTEXT_DEFAULT):
# Always Default context for button label :/
k = ctxt_to_msg[BLF_I18NCONTEXT_DEFAULT].copy()
found = False
for bl in blbls:
if bl in msgid_to_msg:
k &= msgid_to_msg[bl]
found = True
break
elif bl in msgstr_to_msg:
k &= msgstr_to_msg[bl]
found = True
break
if not found:
k = set()
# XXX No need to check against RNA path here, if blabel is different
# from rlabel, should not match anyway!
msgmap["but_label"]["key"] = k
# Tips (they never have a specific context).
etip = getattr(obj, msgmap["enum_tip"]["msgstr"])
print("enum tip: %r" % etip)
if etip:
k = ctxt_to_msg[BLF_I18NCONTEXT_DEFAULT].copy()
if etip in msgid_to_msg:
k &= msgid_to_msg[etip]
elif etip in msgstr_to_msg:
k &= msgstr_to_msg[etip]
else:
k = set()
# We assume if we already have only one key, it's the good one!
if len(k) > 1 and src_enum in src_to_msg:
k &= src_to_msg[src_enum]
msgmap["enum_tip"]["key"] = k
rtip = getattr(obj, msgmap["rna_tip"]["msgstr"])
print("rna tip: %r" % rtip)
if rtip:
k = ctxt_to_msg[BLF_I18NCONTEXT_DEFAULT].copy()
if k and rtip in msgid_to_msg:
k &= msgid_to_msg[rtip]
elif k and rtip in msgstr_to_msg:
k &= msgstr_to_msg[rtip]
else:
k = set()
# We assume if we already have only one key, it's the good one!
if len(k) > 1 and src_rna in src_to_msg:
k &= src_to_msg[src_rna]
msgmap["rna_tip"]["key"] = k
print(k)
btip = getattr(obj, msgmap["but_tip"]["msgstr"])
print("button tip: %r" % btip)
if btip and btip not in {rtip, etip}:
k = ctxt_to_msg[BLF_I18NCONTEXT_DEFAULT].copy()
if btip in msgid_to_msg:
k &= msgid_to_msg[btip]
elif btip in msgstr_to_msg:
k &= msgstr_to_msg[btip]
else:
k = set()
# XXX No need to check against RNA path here, if btip is different
# from rtip, should not match anyway!
msgmap["but_tip"]["key"] = k
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment