diff options
author | Bastien Montagne <montagne29@wanadoo.fr> | 2013-04-09 12:59:56 +0400 |
---|---|---|
committer | Bastien Montagne <montagne29@wanadoo.fr> | 2013-04-09 12:59:56 +0400 |
commit | 906517bac3a5f975fea03969237436340c320ae2 (patch) | |
tree | a5d9f2827a5ed65f4ecb25a5fa66bd4d1967171d /ui_translate | |
parent | 7009865c7e02ee9230a5c46a8ce1896492ee371a (diff) |
Add base for addons' i18n tools. WARNING: nearly nothing is functionnal yet!
Also a few minor edits (and add a version number to this addon!)...
Diffstat (limited to 'ui_translate')
-rw-r--r-- | ui_translate/__init__.py | 6 | ||||
-rw-r--r-- | ui_translate/update_addon.py | 305 | ||||
-rw-r--r-- | ui_translate/update_svn.py | 13 |
3 files changed, 317 insertions, 7 deletions
diff --git a/ui_translate/__init__.py b/ui_translate/__init__.py index 673c4ece..bd34760e 100644 --- a/ui_translate/__init__.py +++ b/ui_translate/__init__.py @@ -21,7 +21,8 @@ bl_info = { "name": "Manage UI translations", "author": "Bastien Montagne", - "blender": (2, 65, 10), + "version": (1, 0, 1), + "blender": (2, 66, 5), "location": "Main \"File\" menu, text editor, any UI control", "description": "Allow to manage UI translations directly from Blender (update main po files, " "update scripts' translations, etc.)", @@ -37,11 +38,13 @@ if "bpy" in locals(): imp.reload(settings) imp.reload(edit_translation) imp.reload(update_svn) + imp.reload(update_addon) else: import bpy from . import settings from . import edit_translation from . import update_svn + from . import update_addon import os @@ -62,4 +65,5 @@ def register(): def unregister(): del bpy.types.WindowManager.i18n_update_svn_settings + bpy.utils.unregister_module(__name__) diff --git a/ui_translate/update_addon.py b/ui_translate/update_addon.py new file mode 100644 index 00000000..5e3b3611 --- /dev/null +++ b/ui_translate/update_addon.py @@ -0,0 +1,305 @@ +# ##### 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) + imp.reload(bl_extract_messages) +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 bl_i18n_utils import bl_extract_messages + +from bpy.app.translations import pgettext_iface as iface_ +import addon_utils + +import io +import os +import shutil +import subprocess +import tempfile + + +##### Helpers ##### +def validate_module(op, context): + module_name = op.module_name + addon = getattr(context, "active_addon", None) + if addon: + module_name = addon.module + + if not module_name: + op.report({'ERROR'}, "No addon module given!") + return None, None + + mod = utils_i18n.enable_addons(addons={module_name}, check_only=True) + if not mod: + op.report({'ERROR'}, "Addon '{}' not found!".format(module_name)) + return None, None + 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. +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 + return items + + +##### Data ##### + + +##### UI ##### +#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") + + +##### Operators ##### +# This one is a helper one, as we sometimes need another invoke function (like e.g. file selection)... +class UI_OT_i18n_addon_translation_invoke(bpy.types.Operator): + """Wrapper operator which will invoke given op after setting its module_name""" + bl_idname = "ui.i18n_addon_translation_invoke" + bl_label = "Update I18n Addon" + bl_property = "module_name" + + 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 = [] + + def invoke(self, context, event): + print("op_id:", self.op_id) + self.__enum_addons_cache.clear() + context.window_manager.invoke_search_popup(self) + return {'RUNNING_MODAL'} + + def execute(self, context): + print("op_id:", self.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) + +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)""" + bl_idname = "ui.i18n_addon_translation_update" + bl_label = "Update I18n Addon" + + 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 + i18n_sett = context.window_manager.i18n_update_svn_settings + + module_name, mod = validate_module(self, context) + + # Generate addon-specific messages (no need for another blender instance here, this should not have any + # influence over the final result). + pot = bl_extract_messages.dump_addon_messages(module_name, True, self.settings) + + # Now (try do) get current i18n data from the addon... + path = mod.__file__ + if path.endswith("__init__.py"): + path = os.path.dirname(path) + + trans = utils_i18n.I18n(kind='PY', src=path, settings=self.settings) + + uids = set() + 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 + uids.add(lng.uid) + # For now, add to processed uids all those not found in "official" list, minus "tech" ones. + uids |= (trans.trans.keys() - {lng.uid for lng in i18n_sett.langs} - + {self.settings.PARSER_TEMPLATE_ID, self.settings.PARSER_PY_ID}) + + # And merge! + for uid in uids: + if uid in trans.trans: + 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! + trans.write(kind='PY') + + return {'FINISHED'} + + +class UI_OT_i18n_addon_translation_export(bpy.types.Operator): + """Export given addon's translation data as a PO file""" + bl_idname = "ui.i18n_addon_translation_export" + bl_label = "I18n Addon Export" + + module_name = EnumProperty(items=enum_addons, name="Addon", description="Addon to process", options=set()) + use_export_pot = BoolProperty(name="Export POT", default=True, description="Export (generate) a POT file too") + use_update_existing = BoolProperty(name="Update Existing", default=True, + 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: + return os.path.join(self.directory, "blender.pot") + path = os.path.join(self.directory, uid) + if os.path.isdir(path): + return os.path.join(path, uid + ".po") + return path + ".po" + elif kind == 'PY': + return trans._dst(trans, path, uid, kind) + return path + + 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__) + self.module_name = module_name + context.window_manager.fileselect_add(self) + return {'RUNNING_MODAL'} + + def execute(self, context): + if not hasattr(self, "settings"): + self.settings = settings.settings + i18n_sett = context.window_manager.i18n_update_svn_settings + + module_name, mod = validate_module(self, context) + if not (module_name and mod): + return {'CANCELLED'} + + path = mod.__file__ + if path.endswith("__init__.py"): + path = os.path.dirname(path) + + trans = utils_i18n.I18n(kind='PY', src=path, settings=self.settings) + trans.dst = self._dst + + uids = [self.settings.PARSER_TEMPLATE_ID] if self.use_export_pot else [] + 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 + uid = utils_i18n.find_best_isocode_matches(lng.uid, trans.trans.keys()) + if uid: + uids.append(uid[0]) + + # Try to update existing POs instead of overwriting them, if asked to do so! + if self.use_update_existing: + for uid in uids: + if uid == self.settings.PARSER_TEMPLATE_ID: + continue + path = trans.dst(trans.src[uid], uid, 'PO') + if not os.path.isfile(path): + continue + msgs = utils_i18n.I18nMessages(kind='PO', src=path, settings=self.settings) + msgs.update(trans.msgs[self.settings.PARSER_TEMPLATE_ID]) + trans.msgs[uid] = msgs + + trans.write(kind='PO', langs=set(uids)) + + return {'FINISHED'} + + diff --git a/ui_translate/update_svn.py b/ui_translate/update_svn.py index efd46c3b..a76d6bce 100644 --- a/ui_translate/update_svn.py +++ b/ui_translate/update_svn.py @@ -46,11 +46,6 @@ import shutil import subprocess import tempfile -##### Helpers ##### -def find_best_isocode_matches(uid, iso_codes): - tmp = ((e, utils_i18n.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): @@ -137,6 +132,12 @@ class UI_PT_i18n_update_translations_settings(bpy.types.Panel): 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): @@ -164,7 +165,7 @@ class UI_OT_i18n_updatetranslation_svn_init_settings(bpy.types.Operator): 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) + best_po = utils_i18n.find_best_isocode_matches(uid, isocodes) #print(uid, "->", best_po) lng = i18n_sett.langs.add() lng.uid = uid |