Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBastien Montagne <montagne29@wanadoo.fr>2013-02-24 12:52:19 +0400
committerBastien Montagne <montagne29@wanadoo.fr>2013-02-24 12:52:19 +0400
commitb39c8c2650111a4dcd3417baf1742b6fac81a08b (patch)
tree6f51372386c91509b57e93589632890da8e56bdf /ui_translate
parent5b6e54f7d1ff8f2ea13511e023387457b66aa263 (diff)
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!
Diffstat (limited to 'ui_translate')
-rw-r--r--ui_translate/__init__.py311
-rw-r--r--ui_translate/edit_translation.py309
-rw-r--r--ui_translate/settings.py196
-rw-r--r--ui_translate/update_svn.py296
-rw-r--r--ui_translate/utils.py191
5 files changed, 826 insertions, 477 deletions
diff --git a/ui_translate/__init__.py b/ui_translate/__init__.py
index da86a722..673c4ece 100644
--- a/ui_translate/__init__.py
+++ b/ui_translate/__init__.py
@@ -19,308 +19,47 @@
# <pep8 compliant>
bl_info = {
- "name": "Translate UI Messages",
+ "name": "Manage UI translations",
"author": "Bastien Montagne",
- "blender": (2, 63, 0),
- "location": "Any UI control",
- "description": "Allow to translate UI directly from Blender",
- "warning": "Broken in this release!",
- "wiki_url": "",
- "tracker_url": "",
+ "blender": (2, 65, 10),
+ "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.)",
+ "warning": "Still in development, not all features are fully implemented yet!",
+ "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',
"category": "System"}
+
if "bpy" in locals():
import imp
- if "ui_utils" in locals():
- imp.reload(ui_utils)
+ imp.reload(settings)
+ imp.reload(edit_translation)
+ imp.reload(update_svn)
else:
import bpy
- from bpy.props import (BoolProperty,
- CollectionProperty,
- EnumProperty,
- FloatProperty,
- FloatVectorProperty,
- IntProperty,
- PointerProperty,
- StringProperty,
- )
- from . import utils as ui_utils
+ from . import settings
+ from . import edit_translation
+ from . import update_svn
-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 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():
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():
+ del bpy.types.WindowManager.i18n_update_svn_settings
bpy.utils.unregister_module(__name__)
-
-
-if __name__ == "__main__":
- register()
diff --git a/ui_translate/edit_translation.py b/ui_translate/edit_translation.py
new file mode 100644
index 00000000..02cde75b
--- /dev/null
+++ b/ui_translate/edit_translation.py
@@ -0,0 +1,309 @@
+# ##### 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)
diff --git a/ui_translate/settings.py b/ui_translate/settings.py
new file mode 100644
index 00000000..bd971f44
--- /dev/null
+++ b/ui_translate/settings.py
@@ -0,0 +1,196 @@
+# ##### 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...")
diff --git a/ui_translate/update_svn.py b/ui_translate/update_svn.py
new file mode 100644
index 00000000..946e7e4f
--- /dev/null
+++ b/ui_translate/update_svn.py
@@ -0,0 +1,296 @@
+# ##### 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'}
diff --git a/ui_translate/utils.py b/ui_translate/utils.py
deleted file mode 100644
index b852f641..00000000
--- a/ui_translate/utils.py
+++ /dev/null
@@ -1,191 +0,0 @@
-# ##### 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