diff --git a/ui_translate/__init__.py b/ui_translate/__init__.py index bd34760e27a2c8be544b6dd45b9b82faf3a6fa92..d374044c4505a65f415dad8515a497b9685594d4 100644 --- a/ui_translate/__init__.py +++ b/ui_translate/__init__.py @@ -39,12 +39,15 @@ if "bpy" in locals(): imp.reload(edit_translation) imp.reload(update_svn) imp.reload(update_addon) + imp.reload(update_ui) else: import bpy - from . import settings - from . import edit_translation - from . import update_svn - from . import update_addon + from . import (settings, + edit_translation, + update_svn, + update_addon, + update_ui, + ) import os @@ -53,7 +56,7 @@ import os def register(): bpy.utils.register_module(__name__) bpy.types.WindowManager.i18n_update_svn_settings = \ - bpy.props.PointerProperty(type=update_svn.I18nUpdateTranslationSettings) + bpy.props.PointerProperty(type=update_ui.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 :( ). diff --git a/ui_translate/update_addon.py b/ui_translate/update_addon.py index 5e3b36110cc648236688ae6643486943183d6444..3a7df05d8047d572d64abb983a9c628e732c2296 100644 --- a/ui_translate/update_addon.py +++ b/ui_translate/update_addon.py @@ -66,29 +66,26 @@ def validate_module(op, context): return module_name, mod[0] -# As it's a bit time heavy, we cache that enum, operators using this should invalidate the cache in Invoke func -# at least. +# As it's a bit time heavy, I'd like to cache that enum, but this does not seem easy to do! :/ +# That "self" is not the same thing as the "self" that operators get in their invoke/execute/etc. funcs... :( def enum_addons(self, context): - items = getattr(self.__class__, "__enum_addons_cache", []) - print(items) - if not items: - setts = getattr(self, "settings", settings.settings) - for mod in addon_utils.modules(addon_utils.addons_fake_modules): - mod_info = addon_utils.module_bl_info(mod) - # Skip OFFICIAL addons, they are already translated in main i18n system (together with Blender itself). - if mod_info["support"] in {'OFFICIAL'}: - continue - src = mod.__file__ - if src.endswith("__init__.py"): - src = os.path.dirname(src) - has_translation, _ = utils_i18n.I18n.check_py_module_has_translations(src, setts) - name = mod_info["name"] - #if has_translation: - #name = name + " *" - items.append((mod.__name__, name, mod_info["description"])) - items.sort(key=lambda i: i[1]) - if hasattr(self.__class__, "__enum_addons_cache"): - self.__class__.__enum_addons_cache = items + setts = getattr(self, "settings", settings.settings) + items = [] + for mod in addon_utils.modules(addon_utils.addons_fake_modules): + mod_info = addon_utils.module_bl_info(mod) + # Skip OFFICIAL addons, they are already translated in main i18n system (together with Blender itself). + if mod_info["support"] in {'OFFICIAL'}: + continue + src = mod.__file__ + if src.endswith("__init__.py"): + src = os.path.dirname(src) + has_translation, _ = utils_i18n.I18n.check_py_module_has_translations(src, setts) + name = mod_info["name"] + # XXX Gives ugly UUUUUUUUUUUUUUUUUUU in search list! + #if has_translation: + #name = name + " *" + items.append((mod.__name__, name, mod_info["description"])) + items.sort(key=lambda i: i[1]) return items @@ -151,24 +148,31 @@ class UI_OT_i18n_addon_translation_invoke(bpy.types.Operator): module_name = EnumProperty(items=enum_addons, name="Addon", description="Addon to process", options=set()) op_id = StringProperty(name="Operator Name", description="Name (id) of the operator to invoke") - - __enum_addons_cache = [] + # XXX Ugly hack! invoke_search_popup does not preserve ops' properties :( + _op_id = "" def invoke(self, context, event): print("op_id:", self.op_id) - self.__enum_addons_cache.clear() + # XXX Ugly hack! invoke_search_popup does not preserve ops' properties :( + self.__class__._op_id = self.op_id context.window_manager.invoke_search_popup(self) return {'RUNNING_MODAL'} def execute(self, context): - print("op_id:", self.op_id) + print("op_id:", self.op_id, self.__class__._op_id) + if not self.op_id: + # XXX Ugly hack! invoke_search_popup does not preserve ops' properties :( + if not self.__class__._op_id: + return {'CANCELLED'} + self.op_id = self.__class__._op_id + self.__class__._op_id = "" op = bpy.ops for item in self.op_id.split('.'): op = getattr(op, item, None) print(self.op_id, item, op) if op is None: return {'CANCELLED'} - op('INVOKE_DEFAULT', module_name=self.module_name) + return op('INVOKE_DEFAULT', module_name=self.module_name) class UI_OT_i18n_addon_translation_update(bpy.types.Operator): """Update given addon's translation data (found as a py tuple in the addon's source code)""" @@ -177,8 +181,6 @@ class UI_OT_i18n_addon_translation_update(bpy.types.Operator): module_name = EnumProperty(items=enum_addons, name="Addon", description="Addon to process", options=set()) - __enum_addons_cache = [] - def execute(self, context): if not hasattr(self, "settings"): self.settings = settings.settings @@ -212,8 +214,9 @@ class UI_OT_i18n_addon_translation_update(bpy.types.Operator): # And merge! for uid in uids: - if uid in trans.trans: - trans.trans[uid].update(pot, keep_old_commented=False) + if uid not in trans.trans: + trans.trans[uid] = utils_i18n.I18nMessages(uid=uid, settings=self.settings) + trans.trans[uid].update(pot, keep_old_commented=False) trans.trans[self.settings.PARSER_TEMPLATE_ID] = pot # For now we write all languages found in this trans! @@ -233,8 +236,6 @@ class UI_OT_i18n_addon_translation_export(bpy.types.Operator): description="Update existing po files, if any, instead of overwriting them") directory = StringProperty(maxlen=1024, subtype='FILE_PATH', options={'HIDDEN', 'SKIP_SAVE'}) - __enum_addons_cache = [] - def _dst(self, trans, path, uid, kind): if kind == 'PO': if uid == self.settings.PARSER_TEMPLATE_ID: @@ -250,7 +251,6 @@ class UI_OT_i18n_addon_translation_export(bpy.types.Operator): def invoke(self, context, event): if not hasattr(self, "settings"): self.settings = settings.settings - self.__enum_addons_cache.clear() module_name, mod = validate_module(self, context) if mod: self.directory = os.path.dirname(mod.__file__) @@ -291,7 +291,7 @@ class UI_OT_i18n_addon_translation_export(bpy.types.Operator): for uid in uids: if uid == self.settings.PARSER_TEMPLATE_ID: continue - path = trans.dst(trans.src[uid], uid, 'PO') + path = trans.dst(trans, trans.src[uid], uid, 'PO') if not os.path.isfile(path): continue msgs = utils_i18n.I18nMessages(kind='PO', src=path, settings=self.settings) diff --git a/ui_translate/update_svn.py b/ui_translate/update_svn.py index a76d6bceb22ac460976ef19e3296d6a881b2e337..bb9bbdb2368472c945a61b7f1ea573391c552c16 100644 --- a/ui_translate/update_svn.py +++ b/ui_translate/update_svn.py @@ -38,8 +38,6 @@ else: from bl_i18n_utils import utils as utils_i18n from bl_i18n_utils import utils_languages_menu -from bpy.app.translations import pgettext_iface as iface_ - import io import os import shutil @@ -47,176 +45,7 @@ import subprocess import tempfile -##### 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=8) - col = split.column() - col.operator("ui.i18n_updatetranslation_svn_init_settings", text="Reset Settings") - if any(l.use for l in i18n_sett.langs): - col.operator("ui.i18n_updatetranslation_svn_settings_select", text="Deselect All").use_select = False - else: - col.operator("ui.i18n_updatetranslation_svn_settings_select", text="Select All").use_select = True - col.operator("ui.i18n_updatetranslation_svn_settings_select", text="Invert Selection").use_invert = True - col.separator() - col.operator("ui.i18n_updatetranslation_svn_branches", text="Update Branches") - col.operator("ui.i18n_updatetranslation_svn_trunk", text="Update Trunk") - col.operator("ui.i18n_updatetranslation_svn_statistics", text="Statistics") - - 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") - - layout.separator() - layout.label("Addons:") - row = layout.row() - op = row.operator("UI_OT_i18n_addon_translation_invoke", text="Export PO...") - op.op_id = "ui.i18n_addon_translation_export" - - ##### 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 = utils_i18n.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 = utils_i18n.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(bpy.types.Operator): - """(De)select (or invert selection of) all languages for i18n svn's update operators""" - bl_idname = "ui.i18n_updatetranslation_svn_settings_select" - bl_label = "Init I18n Update Select Languages" - - use_select = BoolProperty(name="Select All", default=True, description="Select all if True, else deselect all") - use_invert = BoolProperty(name="Invert Selection", default=False, - description="Inverse selection (overrides 'Select All' when True)") - - @classmethod - def poll(cls, context): - return context.window_manager != None - - def execute(self, context): - if self.use_invert: - for lng in context.window_manager.i18n_update_svn_settings.langs: - lng.use = not lng.use - else: - 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" diff --git a/ui_translate/update_ui.py b/ui_translate/update_ui.py new file mode 100644 index 0000000000000000000000000000000000000000..758f7cbe2a32f73a768b82429be6254812a8c6df --- /dev/null +++ b/ui_translate/update_ui.py @@ -0,0 +1,213 @@ +# ##### 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(utils_i18n) +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 utils_i18n + +from bpy.app.translations import pgettext_iface as iface_ + +import os + + +##### 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" + 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=8) + col = split.column() + col.operator("ui.i18n_updatetranslation_svn_init_settings", text="Reset Settings") + if any(l.use for l in i18n_sett.langs): + col.operator("ui.i18n_updatetranslation_svn_settings_select", text="Deselect All").use_select = False + else: + col.operator("ui.i18n_updatetranslation_svn_settings_select", text="Select All").use_select = True + col.operator("ui.i18n_updatetranslation_svn_settings_select", text="Invert Selection").use_invert = True + col.separator() + col.operator("ui.i18n_updatetranslation_svn_branches", text="Update Branches") + col.operator("ui.i18n_updatetranslation_svn_trunk", text="Update Trunk") + col.operator("ui.i18n_updatetranslation_svn_statistics", text="Statistics") + + 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") + + layout.separator() + layout.label("Addons:") + row = layout.row() + op = row.operator("ui.i18n_addon_translation_invoke", text="Export PO...") + op.op_id = "ui.i18n_addon_translation_export" + op = row.operator("ui.i18n_addon_translation_invoke", text="Refresh I18n Data...") + op.op_id = "ui.i18n_addon_translation_update" + + +##### 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 = utils_i18n.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 = utils_i18n.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(bpy.types.Operator): + """(De)select (or invert selection of) all languages for i18n svn's update operators""" + bl_idname = "ui.i18n_updatetranslation_svn_settings_select" + bl_label = "Init I18n Update Select Languages" + + use_select = BoolProperty(name="Select All", default=True, description="Select all if True, else deselect all") + use_invert = BoolProperty(name="Invert Selection", default=False, + description="Inverse selection (overrides 'Select All' when True)") + + @classmethod + def poll(cls, context): + return context.window_manager != None + + def execute(self, context): + if self.use_invert: + for lng in context.window_manager.i18n_update_svn_settings.langs: + lng.use = not lng.use + else: + for lng in context.window_manager.i18n_update_svn_settings.langs: + lng.use = self.use_select + return {'FINISHED'}