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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBastien Montagne <montagne29@wanadoo.fr>2013-04-09 12:56:35 +0400
committerBastien Montagne <montagne29@wanadoo.fr>2013-04-09 12:56:35 +0400
commit6382f6bedd4996962e84edc645c73b31b8bcbbb2 (patch)
tree42f1b47e8b38af5a56307c3f00ed66286e928569 /release/scripts/modules
parent2f5eaf3fcffab246e8d91ff4cd3b918f92b649cd (diff)
Various edits preparing addons' translations tools (not everything yet functionnal/tested, though).
Also workaround a nasty bug, where unregistered py classes remain listed in relevant __subclasses__() calls, which would lead to crash with python addons i18n tools (main translation was not affected, as messages extracting tools are executed in a brand new "factory startup" Blender ;) ).
Diffstat (limited to 'release/scripts/modules')
-rw-r--r--release/scripts/modules/bl_i18n_utils/bl_extract_messages.py217
-rw-r--r--release/scripts/modules/bl_i18n_utils/utils.py156
2 files changed, 222 insertions, 151 deletions
diff --git a/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py b/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py
index 26b6845530d..25f8d0c7ebb 100644
--- a/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py
+++ b/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py
@@ -62,6 +62,17 @@ def _gen_check_ctxt(settings):
"spell_errors": {},
}
+def _diff_check_ctxt(check_ctxt, minus_check_ctxt):
+ """Returns check_ctxt - minus_check_ctxt"""
+ for key in check_ctxt:
+ if isinstance(check_ctxt[key], set):
+ for warning in minus_check_ctxt[key]:
+ if warning in check_ctxt[key]:
+ check_ctxt[key].remove(warning)
+ elif isinstance(check_ctxt[key], dict):
+ for warning in minus_check_ctxt[key]:
+ if warning in check_ctxt[key]:
+ del check_ctxt[key][warning]
def _gen_reports(check_ctxt):
return {
@@ -176,45 +187,6 @@ def print_info(reports, pot):
_print("\t\t{}".format("\n\t\t".join(pot.msgs[key].sources)))
-def enable_addons(addons={}, support={}, disable=False):
- """
- Enable (or disable) addons based either on a set of names, or a set of 'support' types.
- Returns the list of all affected addons (as fake modules)!
- """
- import addon_utils
-
- userpref = bpy.context.user_preferences
- used_ext = {ext.module for ext in userpref.addons}
-
- ret = [mod for mod in addon_utils.modules(addon_utils.addons_fake_modules)
- if ((addons and mod.__name__ in addons) or
- (not addons and addon_utils.module_bl_info(mod)["support"] in support))]
-
- for mod in ret:
- module_name = mod.__name__
- if disable:
- if module_name not in used_ext:
- continue
- print(" Disabling module ", module_name)
- bpy.ops.wm.addon_disable(module=module_name)
- else:
- if module_name in used_ext:
- continue
- print(" Enabling module ", module_name)
- bpy.ops.wm.addon_enable(module=module_name)
-
- # XXX There are currently some problems with bpy/rna...
- # *Very* tricky to solve!
- # So this is a hack to make all newly added operator visible by
- # bpy.types.OperatorProperties.__subclasses__()
- for cat in dir(bpy.ops):
- cat = getattr(bpy.ops, cat)
- for op in dir(cat):
- getattr(cat, op).get_rna()
-
- return ret
-
-
def process_msg(msgs, msgctxt, msgid, msgsrc, reports, check_ctxt, settings):
if filter_message(msgid):
reports["messages_skipped"].add((msgid, msgsrc))
@@ -235,50 +207,72 @@ def process_msg(msgs, msgctxt, msgid, msgsrc, reports, check_ctxt, settings):
##### RNA #####
-def dump_messages_rna(msgs, reports, settings):
+def dump_rna_messages(msgs, reports, settings):
"""
Dump into messages dict all RNA-defined UI messages (labels en tooltips).
"""
def class_blacklist():
- blacklist_rna_class = [
- # core classes
- "Context", "Event", "Function", "UILayout", "UnknownType",
- # registerable classes
- "Panel", "Menu", "Header", "RenderEngine", "Operator", "OperatorMacro", "Macro", "KeyingSetInfo",
- # window classes
- "Window",
- ]
+ blacklist_rna_class = {getattr(bpy.types, cls_id) for cls_id in (
+ # core classes
+ "Context", "Event", "Function", "UILayout", "UnknownType", "Property", "Struct",
+ # registerable classes
+ "Panel", "Menu", "Header", "RenderEngine", "Operator", "OperatorMacro", "Macro", "KeyingSetInfo",
+ # window classes
+ "Window",
+ )
+ }
- # Collect internal operators
- # extend with all internal operators
- # note that this uses internal api introspection functions
- # all possible operator names
- op_ids = set(cls.bl_rna.identifier for cls in bpy.types.OperatorProperties.__subclasses__()) | \
- set(cls.bl_rna.identifier for cls in bpy.types.Operator.__subclasses__()) | \
- set(cls.bl_rna.identifier for cls in bpy.types.OperatorMacro.__subclasses__())
-
- get_instance = __import__("_bpy").ops.get_instance
-# path_resolve = type(bpy.context).__base__.path_resolve
- for idname in op_ids:
- op = get_instance(idname)
- # XXX Do not skip INTERNAL's anymore, some of those ops show up in UI now!
-# if 'INTERNAL' in path_resolve(op, "bl_options"):
-# blacklist_rna_class.append(idname)
-
- # Collect builtin classes we don't need to doc
- blacklist_rna_class.append("Property")
- blacklist_rna_class.extend([cls.__name__ for cls in bpy.types.Property.__subclasses__()])
-
- # Collect classes which are attached to collections, these are api access only.
- collection_props = set()
- for cls_id in dir(bpy.types):
- cls = getattr(bpy.types, cls_id)
+ # More builtin classes we don't need to parse.
+ blacklist_rna_class |= {cls for cls in bpy.types.Property.__subclasses__()}
+
+ _rna = {getattr(bpy.types, cls) for cls in dir(bpy.types)}
+
+ # Classes which are attached to collections can be skipped too, these are api access only.
+ for cls in _rna:
for prop in cls.bl_rna.properties:
if prop.type == 'COLLECTION':
prop_cls = prop.srna
if prop_cls is not None:
- collection_props.add(prop_cls.identifier)
- blacklist_rna_class.extend(sorted(collection_props))
+ blacklist_rna_class.add(prop_cls.__class__)
+
+ # Now here is the *ugly* hack!
+ # Unfortunately, all classes we want to access are not available from bpy.types (OperatorProperties subclasses
+ # are not here, as they have the same name as matching Operator ones :( ). So we use __subclasses__() calls
+ # to walk through all rna hierachy.
+ # But unregistered classes remain listed by relevant __subclasses__() calls (be it a Py or BPY/RNA bug),
+ # and obviously the matching RNA struct exists no more, so trying to access their data (even the identifier)
+ # quickly leads to segfault!
+ # To address this, we have to blacklist classes which __name__ does not match any __name__ from bpy.types
+ # (we can't use only RNA identifiers, as some py-defined classes has a different name that rna id,
+ # and we can't use class object themselves, because OperatorProperties subclasses are not in bpy.types!)...
+
+ _rna_clss_ids = {cls.__name__ for cls in _rna} | {cls.bl_rna.identifier for cls in _rna}
+
+ # All registrable types.
+ blacklist_rna_class |= {cls for cls in bpy.types.OperatorProperties.__subclasses__() +
+ bpy.types.Operator.__subclasses__() +
+ bpy.types.OperatorMacro.__subclasses__() +
+ bpy.types.Header.__subclasses__() +
+ bpy.types.Panel.__subclasses__() +
+ bpy.types.Menu.__subclasses__() +
+ bpy.types.UIList.__subclasses__()
+ if cls.__name__ not in _rna_clss_ids}
+
+ # Collect internal operators
+ # extend with all internal operators
+ # note that this uses internal api introspection functions
+ # XXX Do not skip INTERNAL's anymore, some of those ops show up in UI now!
+ # all possible operator names
+ #op_ids = (set(cls.bl_rna.identifier for cls in bpy.types.OperatorProperties.__subclasses__()) |
+ #set(cls.bl_rna.identifier for cls in bpy.types.Operator.__subclasses__()) |
+ #set(cls.bl_rna.identifier for cls in bpy.types.OperatorMacro.__subclasses__()))
+
+ #get_instance = __import__("_bpy").ops.get_instance
+ #path_resolve = type(bpy.context).__base__.path_resolve
+ #for idname in op_ids:
+ #op = get_instance(idname)
+ #if 'INTERNAL' in path_resolve(op, "bl_options"):
+ #blacklist_rna_class.add(idname)
return blacklist_rna_class
@@ -337,17 +331,6 @@ def dump_messages_rna(msgs, reports, settings):
def walk_class(cls):
bl_rna = cls.bl_rna
- reports["rna_structs"].append(cls)
- if bl_rna.identifier in blacklist_rna_class:
- reports["rna_structs_skipped"].append(cls)
- return
-
- # XXX translation_context of Operator sub-classes are not "good"!
- # So ignore those Operator sub-classes (anyway, will get the same from OperatorProperties sub-classes!)...
- if issubclass(cls, bpy.types.Operator):
- reports["rna_structs_skipped"].append(cls)
- return
-
msgsrc = "bpy.types." + bl_rna.identifier
msgctxt = bl_rna.translation_context or default_context
@@ -388,7 +371,12 @@ def dump_messages_rna(msgs, reports, settings):
cls_list.sort(key=full_class_id)
for cls in cls_list:
- walk_class(cls)
+ reports["rna_structs"].append(cls)
+ # Ignore those Operator sub-classes (anyway, will get the same from OperatorProperties sub-classes!)...
+ if (cls in blacklist_rna_class) or issubclass(cls, bpy.types.Operator):
+ reports["rna_structs_skipped"].append(cls)
+ else:
+ walk_class(cls)
# Recursively process subclasses.
process_cls_list(cls.__subclasses__())
@@ -796,14 +784,14 @@ def dump_messages(do_messages, do_checks, settings):
# Enable all wanted addons.
# For now, enable all official addons, before extracting msgids.
- addons = enable_addons(support={"OFFICIAL"})
+ addons = utils.enable_addons(support={"OFFICIAL"})
# Note this is not needed if we have been started with factory settings, but just in case...
- enable_addons(support={"COMMUNITY", "TESTING"}, disable=True)
+ utils.enable_addons(support={"COMMUNITY", "TESTING"}, disable=True)
reports = _gen_reports(_gen_check_ctxt(settings) if do_checks else None)
# Get strings from RNA.
- dump_messages_rna(msgs, reports, settings)
+ dump_rna_messages(msgs, reports, settings)
# Get strings from UI layout definitions text="..." args.
dump_py_messages(msgs, reports, addons, settings)
@@ -836,40 +824,51 @@ def dump_messages(do_messages, do_checks, settings):
print("Finished extracting UI messages!")
+ return pot # Not used currently, but may be useful later (and to be consistent with dump_addon_messages!).
+
-def dump_addon_messages(module_name, messages_formats, do_checks, settings):
- # Enable our addon and get strings from RNA.
- addon = enable_addons(addons={module_name})[0]
+def dump_addon_messages(module_name, do_checks, settings):
+ import addon_utils
+
+ # Get current addon state (loaded or not):
+ was_loaded = addon_utils.check(module_name)[1]
+
+ # Enable our addon.
+ addon = utils.enable_addons(addons={module_name})[0]
addon_info = addon_utils.module_bl_info(addon)
- ver = addon_info.name + " " + ".".join(addon_info.version)
- rev = "???"
- date = datetime.datetime()
+ ver = addon_info["name"] + " " + ".".join(str(v) for v in addon_info["version"])
+ rev = 0
+ date = datetime.datetime.now()
pot = utils.I18nMessages.gen_empty_messages(settings.PARSER_TEMPLATE_ID, ver, rev, date, date.year,
settings=settings)
msgs = pot.msgs
- minus_msgs = copy.deepcopy(msgs)
+ minus_pot = utils.I18nMessages.gen_empty_messages(settings.PARSER_TEMPLATE_ID, ver, rev, date, date.year,
+ settings=settings)
+ minus_msgs = minus_pot.msgs
check_ctxt = _gen_check_ctxt(settings) if do_checks else None
minus_check_ctxt = _gen_check_ctxt(settings) if do_checks else None
- # Get current addon state (loaded or not):
- was_loaded = addon_utils.check(module_name)[1]
-
- # Enable our addon and get strings from RNA.
- addons = enable_addons(addons={module_name})
+ # Get strings from RNA, our addon being enabled.
+ print("A")
reports = _gen_reports(check_ctxt)
- dump_messages_rna(msgs, reports, settings)
+ print("B")
+ dump_rna_messages(msgs, reports, settings)
+ print("C")
# Now disable our addon, and rescan RNA.
- enable_addons(addons={module_name}, disable=True)
+ utils.enable_addons(addons={module_name}, disable=True)
+ print("D")
reports["check_ctxt"] = minus_check_ctxt
- dump_messages_rna(minus_msgs, reports, settings)
+ print("E")
+ dump_rna_messages(minus_msgs, reports, settings)
+ print("F")
# Restore previous state if needed!
if was_loaded:
- enable_addons(addons={module_name})
+ utils.enable_addons(addons={module_name})
# and make the diff!
for key in minus_msgs:
@@ -877,11 +876,10 @@ def dump_addon_messages(module_name, messages_formats, do_checks, settings):
del msgs[key]
if check_ctxt:
- for key in check_ctxt:
- for warning in minus_check_ctxt[key]:
- check_ctxt[key].remove(warning)
+ check_ctxt = _diff_check_ctxt(check_ctxt, minus_check_ctxt)
# and we are done with those!
+ del minus_pot
del minus_msgs
del minus_check_ctxt
@@ -889,8 +887,11 @@ def dump_addon_messages(module_name, messages_formats, do_checks, settings):
reports["check_ctxt"] = check_ctxt
dump_py_messages(msgs, reports, {addon}, settings, addons_only=True)
+ pot.unescape() # Strings gathered in py/C source code may contain escaped chars...
print_info(reports, pot)
+ print("Finished extracting UI messages!")
+
return pot
diff --git a/release/scripts/modules/bl_i18n_utils/utils.py b/release/scripts/modules/bl_i18n_utils/utils.py
index 5e9464b782a..bf1c7a44db3 100644
--- a/release/scripts/modules/bl_i18n_utils/utils.py
+++ b/release/scripts/modules/bl_i18n_utils/utils.py
@@ -122,6 +122,52 @@ def locale_match(loc1, loc2):
return ...
+def find_best_isocode_matches(uid, iso_codes):
+ tmp = ((e, 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]))
+
+
+def enable_addons(addons={}, support={}, disable=False, check_only=False):
+ """
+ Enable (or disable) addons based either on a set of names, or a set of 'support' types.
+ Returns the list of all affected addons (as fake modules)!
+ If "check_only" is set, no addon will be enabled nor disabled.
+ """
+ import addon_utils
+
+ userpref = bpy.context.user_preferences
+ used_ext = {ext.module for ext in userpref.addons}
+
+ ret = [mod for mod in addon_utils.modules(addon_utils.addons_fake_modules)
+ if ((addons and mod.__name__ in addons) or
+ (not addons and addon_utils.module_bl_info(mod)["support"] in support))]
+
+ if not check_only:
+ for mod in ret:
+ module_name = mod.__name__
+ if disable:
+ if module_name not in used_ext:
+ continue
+ print(" Disabling module ", module_name)
+ bpy.ops.wm.addon_disable(module=module_name)
+ else:
+ if module_name in used_ext:
+ continue
+ print(" Enabling module ", module_name)
+ bpy.ops.wm.addon_enable(module=module_name)
+
+ # XXX There are currently some problems with bpy/rna...
+ # *Very* tricky to solve!
+ # So this is a hack to make all newly added operator visible by
+ # bpy.types.OperatorProperties.__subclasses__()
+ for cat in dir(bpy.ops):
+ cat = getattr(bpy.ops, cat)
+ for op in dir(cat):
+ getattr(cat, op).get_rna()
+
+ return ret
+
+
##### Main Classes #####
class I18nMessage:
@@ -1021,7 +1067,9 @@ class I18n:
"""
@staticmethod
- def _parser_check_file(path, maxsize=settings.PARSER_MAX_FILE_SIZE, _begin_marker=None, _end_marker=None):
+ def _parser_check_file(path, maxsize=settings.PARSER_MAX_FILE_SIZE,
+ _begin_marker=settings.PARSER_PY_MARKER_BEGIN,
+ _end_marker=settings.PARSER_PY_MARKER_END):
if os.stat(path).st_size > maxsize:
# Security, else we could read arbitrary huge files!
print("WARNING: skipping file {}, too huge!".format(path))
@@ -1040,8 +1088,16 @@ class I18n:
if _end_marker in txt:
_out = txt.index(_end_marker)
if _in is not None and _out is not None:
- return txt[:_in], txt[_in:_out], txt[_out:]
- return txt, None, None
+ in_txt, txt, out_txt = txt[:_in], txt[_in:_out], txt[_out:]
+ elif _in is not None:
+ in_txt, txt, out_txt = txt[:_in], txt[_in:], None
+ elif _out is not None:
+ in_txt, txt, out_txt = None, txt[:_out], txt[_out:]
+ else:
+ in_txt, txt, out_txt = None, txt, None
+ if "translations_tuple" not in txt:
+ return None, None, None
+ return in_txt, txt, out_txt
@staticmethod
def _dst(self, path, uid, kind):
@@ -1148,6 +1204,35 @@ class I18n:
)
print(prefix.join(lines))
+ @classmethod
+ def check_py_module_has_translations(clss, src, settings=settings):
+ """
+ Check whether a given src (a py module, either a directory or a py file) has some i18n translation data,
+ and returns a tuple (src_file, translations_tuple) if yes, else (None, None).
+ """
+ txts = []
+ if os.path.isdir(src):
+ for root, dnames, fnames in os.walk(src):
+ for fname in fnames:
+ if not fname.endswith(".py"):
+ continue
+ path = os.path.join(root, fname)
+ _1, txt, _2 = clss._parser_check_file(path)
+ if txt is not None:
+ txts.append((path, txt))
+ elif src.endswith(".py") and os.path.isfile(src):
+ _1, txt, _2 = clss._parser_check_file(src)
+ if txt is not None:
+ txts.append((src, txt))
+ for path, txt in txts:
+ tuple_id = "translations_tuple"
+ env = globals().copy()
+ exec(txt, env)
+ if tuple_id in env:
+ return path, env[tuple_id]
+ return None, None # No data...
+
+
def parse(self, kind, src, langs=set()):
self.parsers[kind](self, src, langs)
@@ -1193,28 +1278,9 @@ class I18n:
if langs set is void, all languages found are loaded.
"""
default_context = self.settings.DEFAULT_CONTEXT
- txt = None
- if os.path.isdir(src):
- for root, dnames, fnames in os.walk(src):
- for fname in fnames:
- path = os.path.join(root, fname)
- _1, txt, _2 = self._parser_check_file(path)
- if txt is not None:
- self.src[self.settings.PARSER_PY_ID] = path
- break
- if txt is not None:
- break
- elif src.endswith(".py") and os.path.isfile(src):
- _1, txt, _2 = _check_file(src, self.settings.PARSER_PY_MARKER_BEGIN, self.settings.PARSER_PY_MARKER_END)
- if txt is not None:
- self.src[self.settings.PARSER_PY_ID] = src
- if txt is None:
+ self.src[self.settings.PARSER_PY_ID], msgs = self.check_py_module_has_translations(src, self.settings)
+ if msgs is None:
return
- env = globals()
- exec(txt, env)
- if "translations_tuple" not in env:
- return # No data...
- msgs = env["translations_tuple"]
for key, (sources, gen_comments), *translations in msgs:
if self.settings.PARSER_TEMPLATE_ID not in self.trans:
self.trans[self.settings.PARSER_TEMPLATE_ID] = I18nMessages(self.settings.PARSER_TEMPLATE_ID,
@@ -1239,6 +1305,10 @@ class I18n:
comment_lines = [self.settings.PO_COMMENT_PREFIX + c for c in user_comments] + common_comment_lines
self.trans[uid].msgs[key] = I18nMessage(ctxt, [key[1]], [msgstr], comment_lines, False, is_fuzzy,
settings=self.settings)
+ #key = self.settings.PO_HEADER_KEY
+ #for uid, trans in self.trans.items():
+ #if key not in trans.msgs:
+ #trans.msgs[key]
self.unescape()
def write(self, kind, langs=set()):
@@ -1261,7 +1331,7 @@ class I18n:
def write_to_py(self, langs=set()):
"""
Write all translations as python code, either in a "translations.py" file under same dir as source(s), or in
- specified file is self.py_file is set (default, as usual can be customized with self.dst callable!).
+ specified file if self.py_file is set (default, as usual can be customized with self.dst callable!).
Note: If langs is set and you want to export the pot template as well, langs must contain PARSER_TEMPLATE_ID
({} currently).
""".format(self.settings.PARSER_TEMPLATE_ID)
@@ -1282,16 +1352,16 @@ class I18n:
]
# First gather all keys (msgctxt, msgid) - theoretically, all translations should share the same, but...
keys = set()
- for trans in self.trans.items:
- keys |= trans.msgs.keys()
+ for trans in self.trans.values():
+ keys |= set(trans.msgs.keys())
# Get the ref translation (ideally, PARSER_TEMPLATE_ID one, else the first one that pops up!
# Ref translation will be used to generate sources "comments"
ref = self.trans.get(self.settings.PARSER_TEMPLATE_ID) or self.trans[list(self.trans.keys())[0]]
- # Get all languages (uids) and sort them (PARSER_TEMPLATE_ID excluded!)
- translations = self.trans.keys() - {self.settings.PARSER_TEMPLATE_ID}
+ # Get all languages (uids) and sort them (PARSER_TEMPLATE_ID and PARSER_PY_ID excluded!)
+ translations = self.trans.keys() - {self.settings.PARSER_TEMPLATE_ID, self.settings.PARSER_PY_ID}
if langs:
translations &= langs
- translations = [('"' + lng + '"', " " * len(lng) + 4, self.trans[lng]) for lng in sorted(translations)]
+ translations = [('"' + lng + '"', " " * (len(lng) + 4), self.trans[lng]) for lng in sorted(translations)]
for key in keys:
if ref.msgs[key].is_commented:
continue
@@ -1340,9 +1410,9 @@ class I18n:
if len(comments) > 1:
ret.append(tab + lngsp + " (\"" + comments[0] + "\",")
ret += [tab + lngsp + " \"" + s + "\"," for s in comments[1:-1]]
- ret.append(tab + lngsp + " \"" + comments[-1] + "\"))),")
+ ret.append(tab + lngsp + " \"" + comments[-1] + "\")),")
else:
- ret[-1] = ret[-1] + " " + ('"' + comments[0] + '",' if comments else "") + "))),"
+ ret[-1] = ret[-1] + " " + ('"' + comments[0] + '",' if comments else "") + ")),"
ret.append(tab + "),")
ret += [
")",
@@ -1359,20 +1429,20 @@ class I18n:
self.escape(True)
dst = self.dst(self, self.src.get(self.settings.PARSER_PY_ID, ""), self.settings.PARSER_PY_ID, 'PY')
- prev = txt = next = ""
+ print(dst)
+ prev = txt = nxt = ""
if os.path.exists(dst):
if not os.path.isfile(dst):
print("WARNING: trying to write as python code into {}, which is not a file! Aborting.".format(dst))
return
- prev, txt, next = self._parser_check_file(dst, self.settings.PARSER_MAX_FILE_SIZE,
- self.settings.PARSER_PY_MARKER_BEGIN,
- self.settings.PARSER_PY_MARKER_END)
- if prev is None:
- return
- if txt is None:
- print("WARNING: given python file {} has no auto-generated translations yet, will be added at "
- "the end of the file, you can move that section later if needed...".format(dst))
- txt = _gen_py(self, langs)
+ prev, txt, nxt = self._parser_check_file(dst)
+ if prev is None and nxt is None:
+ print("WARNING: Looks like given python file {} has no auto-generated translations yet, will be added "
+ "at the end of the file, you can move that section later if needed...".format(dst))
+ txt = [txt] + _gen_py(self, langs)
+ else:
+ # We completely replace the text found between start and end markers...
+ txt = _gen_py(self, langs)
else:
printf("Creating python file {} containing translations.".format(dst))
txt = [
@@ -1403,7 +1473,7 @@ class I18n:
self.settings.PARSER_PY_MARKER_END,
]
with open(dst, 'w') as f:
- f.write(prev + "\n".join(txt) + (next or ""))
+ f.write(prev + "\n".join(txt) + (nxt or ""))
self.unescape()
parsers = {