diff --git a/ui_translate/__init__.py b/ui_translate/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..77cc41230aa83f6a126537c9ce00c1884fa5d6a9
--- /dev/null
+++ b/ui_translate/__init__.py
@@ -0,0 +1,325 @@
+# ##### 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>
+
+bl_info = {
+    "name": "Translate UI Messages",
+    "author": "Bastien Montagne",
+    "blender": (2, 6, 3),
+    "location": "Any UI control",
+    "description": "Allow to translate UI directly from Blender",
+    "warning": "",
+    "wiki_url": "",
+    "tracker_url": "",
+    "support": 'OFFICIAL',
+    "category": "System"}
+
+if "bpy" in locals():
+    import imp
+    if "ui_utils" in locals():
+        imp.reload(ui_utils)
+else:
+    import bpy
+    from bpy.props import (BoolProperty,
+                           CollectionProperty,
+                           EnumProperty,
+                           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 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:
+            bpy.ops.ui.edittranslation_update_mo(po_file=self.po_file, lang=self.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.but_label or self.rna_label or self.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.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.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.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.but_tip or self.rna_tip or self.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.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.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.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__)
+
+
+def unregister():
+    bpy.utils.unregister_module(__name__)
+
+
+if __name__ == "__main__":
+    register()
diff --git a/ui_translate/utils.py b/ui_translate/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae86e88b030c7ce7673142459d103c4bbb861609
--- /dev/null
+++ b/ui_translate/utils.py
@@ -0,0 +1,192 @@
+# ##### 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, 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: '"+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: '"+rlbl+"'", rlbl in msgid_to_msg, rlbl in msgstr_to_msg)
+    if rlbl:
+        k = ctxt_to_msg[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: '"+blbl+"'")
+    if blbl and elbl not in blbls and (rlbl not in blbls or 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: '"+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: '"+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: '"+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