diff options
author | RĂ©mi Verschelde <remi@verschelde.fr> | 2022-03-10 21:09:50 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-10 21:09:50 +0100 |
commit | 7e4a8d3ab3725b27b1b5dde21f10b59dc08317c8 (patch) | |
tree | fe23d3ccb0e8befb13c027742ac2fa389b2935cc | |
parent | 1c51fd48db03c3983d4726c5f3df3f9d9c9ab4b2 (diff) | |
parent | d6df2ffad8beabc12acccbafd3afa00d7e501d9c (diff) |
Merge pull request #58706 from timothyqiu/property-i18n
-rw-r--r-- | editor/editor_feature_profile.cpp | 4 | ||||
-rw-r--r-- | editor/editor_inspector.cpp | 13 | ||||
-rw-r--r-- | editor/editor_node.cpp | 5 | ||||
-rw-r--r-- | editor/editor_property_name_processor.cpp | 121 | ||||
-rw-r--r-- | editor/editor_property_name_processor.h | 58 | ||||
-rw-r--r-- | editor/editor_sectioned_inspector.cpp | 4 | ||||
-rw-r--r-- | editor/editor_settings_dialog.cpp | 4 | ||||
-rwxr-xr-x | editor/translations/extract.py | 172 |
8 files changed, 294 insertions, 87 deletions
diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp index 008c42b3a7..b1708e756a 100644 --- a/editor/editor_feature_profile.cpp +++ b/editor/editor_feature_profile.cpp @@ -34,6 +34,7 @@ #include "core/io/json.h" #include "editor/editor_file_dialog.h" #include "editor/editor_node.h" +#include "editor/editor_property_name_processor.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" @@ -617,7 +618,8 @@ void EditorFeatureProfileManager::_class_list_item_selected() { property->set_editable(0, true); property->set_selectable(0, true); property->set_checked(0, !edited->is_class_property_disabled(class_name, name)); - property->set_text(0, name.capitalize()); + property->set_text(0, EditorPropertyNameProcessor::get_singleton()->process_name(name)); + property->set_tooltip(0, EditorPropertyNameProcessor::get_singleton()->make_tooltip_for_name(name)); property->set_metadata(0, name); String icon_type = Variant::get_type_name(E.type); property->set_icon(0, EditorNode::get_singleton()->get_class_icon(icon_type)); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index d48ca7f3b3..f5d318a227 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -36,6 +36,7 @@ #include "editor/doc_tools.h" #include "editor/editor_feature_profile.h" #include "editor/editor_node.h" +#include "editor/editor_property_name_processor.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "multi_node_edit.h" @@ -2680,10 +2681,10 @@ void EditorInspector::update_tree() { if (dot != -1) { String ov = property_label_string.substr(dot); property_label_string = property_label_string.substr(0, dot); - property_label_string = property_label_string.capitalize(); + property_label_string = EditorPropertyNameProcessor::get_singleton()->process_name(property_label_string); property_label_string += ov; } else { - property_label_string = property_label_string.capitalize(); + property_label_string = EditorPropertyNameProcessor::get_singleton()->process_name(property_label_string); } } @@ -2734,13 +2735,15 @@ void EditorInspector::update_tree() { current_vbox->add_child(section); sections.push_back(section); + String label = component; if (capitalize_paths) { - component = component.capitalize(); + label = EditorPropertyNameProcessor::get_singleton()->process_name(label); } Color c = sscolor; c.a /= level; - section->setup(acc_path, component, object, c, use_folding, section_depth); + section->setup(acc_path, label, object, c, use_folding, section_depth); + section->set_tooltip(EditorPropertyNameProcessor::get_singleton()->make_tooltip_for_name(component)); // Add editors at the start of a group. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { @@ -2772,7 +2775,7 @@ void EditorInspector::update_tree() { editor_inspector_array = memnew(EditorInspectorArray); String array_label = path.contains("/") ? path.substr(path.rfind("/") + 1) : path; - array_label = property_label_string.capitalize(); + array_label = EditorPropertyNameProcessor::get_singleton()->process_name(property_label_string); int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0; editor_inspector_array->setup_with_move_element_function(object, array_label, array_element_prefix, page, c, use_folding); editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request), varray(array_element_prefix)); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 2c39dc69b9..17b219f8dc 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -90,6 +90,7 @@ #include "editor/editor_paths.h" #include "editor/editor_plugin.h" #include "editor/editor_properties.h" +#include "editor/editor_property_name_processor.h" #include "editor/editor_resource_picker.h" #include "editor/editor_resource_preview.h" #include "editor/editor_run.h" @@ -5803,6 +5804,9 @@ void EditorNode::notify_settings_changed() { } EditorNode::EditorNode() { + EditorPropertyNameProcessor *epnp = memnew(EditorPropertyNameProcessor); + add_child(epnp); + Input::get_singleton()->set_use_accumulated_input(true); Resource::_get_local_scene_func = _resource_get_edited_scene; @@ -6025,6 +6029,7 @@ EditorNode::EditorNode() { EDITOR_DEF("interface/editor/save_on_focus_loss", false); EDITOR_DEF("interface/editor/show_update_spinner", false); EDITOR_DEF("interface/editor/update_continuously", false); + EDITOR_DEF("interface/editor/translate_properties", true); EDITOR_DEF_RST("interface/scene_tabs/restore_scenes_on_load", true); EDITOR_DEF_RST("interface/inspector/capitalize_properties", true); EDITOR_DEF_RST("interface/inspector/default_float_step", 0.001); diff --git a/editor/editor_property_name_processor.cpp b/editor/editor_property_name_processor.cpp new file mode 100644 index 0000000000..38003ab2f4 --- /dev/null +++ b/editor/editor_property_name_processor.cpp @@ -0,0 +1,121 @@ +/*************************************************************************/ +/* editor_property_name_processor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "editor_property_name_processor.h" + +#include "editor_settings.h" + +EditorPropertyNameProcessor *EditorPropertyNameProcessor::singleton = nullptr; + +String EditorPropertyNameProcessor::_capitalize_name(const String &p_name) const { + String capitalized_string = p_name.capitalize(); + + // Fix the casing of a few strings commonly found in editor property/setting names. + for (Map<String, String>::Element *E = capitalize_string_remaps.front(); E; E = E->next()) { + capitalized_string = capitalized_string.replace(E->key(), E->value()); + } + + return capitalized_string; +} + +String EditorPropertyNameProcessor::process_name(const String &p_name) const { + const String capitalized_string = _capitalize_name(p_name); + if (EDITOR_GET("interface/editor/translate_properties")) { + return TTRGET(capitalized_string); + } + return capitalized_string; +} + +String EditorPropertyNameProcessor::make_tooltip_for_name(const String &p_name) const { + const String capitalized_string = _capitalize_name(p_name); + if (EDITOR_GET("interface/editor/translate_properties")) { + return capitalized_string; + } + return TTRGET(capitalized_string); +} + +EditorPropertyNameProcessor::EditorPropertyNameProcessor() { + ERR_FAIL_COND(singleton != nullptr); + singleton = this; + + // The following initialization is parsed in `editor/translations/extract.py` with a regex. + // The map name and value definition format should be kept synced with the regex. + capitalize_string_remaps["2d"] = "2D"; + capitalize_string_remaps["3d"] = "3D"; + capitalize_string_remaps["Adb"] = "ADB"; + capitalize_string_remaps["Bptc"] = "BPTC"; + capitalize_string_remaps["Bvh"] = "BVH"; + capitalize_string_remaps["Csg"] = "CSG"; + capitalize_string_remaps["Cpu"] = "CPU"; + capitalize_string_remaps["Db"] = "dB"; + capitalize_string_remaps["Dof"] = "DoF"; + capitalize_string_remaps["Dpi"] = "DPI"; + capitalize_string_remaps["Etc"] = "ETC"; + capitalize_string_remaps["Fbx"] = "FBX"; + capitalize_string_remaps["Fps"] = "FPS"; + capitalize_string_remaps["Fov"] = "FOV"; + capitalize_string_remaps["Fs"] = "FS"; + capitalize_string_remaps["Fxaa"] = "FXAA"; + capitalize_string_remaps["Ggx"] = "GGX"; + capitalize_string_remaps["Gdscript"] = "GDScript"; + capitalize_string_remaps["Gles 2"] = "GLES2"; + capitalize_string_remaps["Gles 3"] = "GLES3"; + capitalize_string_remaps["Gi Probe"] = "GI Probe"; + capitalize_string_remaps["Hdr"] = "HDR"; + capitalize_string_remaps["Hidpi"] = "hiDPI"; + capitalize_string_remaps["Ik"] = "IK"; + capitalize_string_remaps["Ios"] = "iOS"; + capitalize_string_remaps["Kb"] = "KB"; + capitalize_string_remaps["Msaa"] = "MSAA"; + capitalize_string_remaps["Macos"] = "macOS"; + capitalize_string_remaps["Opentype"] = "OpenType"; + capitalize_string_remaps["Png"] = "PNG"; + capitalize_string_remaps["Pvs"] = "PVS"; + capitalize_string_remaps["Pvrtc"] = "PVRTC"; + capitalize_string_remaps["S 3 Tc"] = "S3TC"; + capitalize_string_remaps["Sdfgi"] = "SDFGI"; + capitalize_string_remaps["Srgb"] = "sRGB"; + capitalize_string_remaps["Ssao"] = "SSAO"; + capitalize_string_remaps["Ssl"] = "SSL"; + capitalize_string_remaps["Ssh"] = "SSH"; + capitalize_string_remaps["Sdk"] = "SDK"; + capitalize_string_remaps["Tcp"] = "TCP"; + capitalize_string_remaps["Uv 1"] = "UV1"; + capitalize_string_remaps["Uv 2"] = "UV2"; + capitalize_string_remaps["Vram"] = "VRAM"; + capitalize_string_remaps["Vsync"] = "V-Sync"; + capitalize_string_remaps["Vector 2"] = "Vector2"; + capitalize_string_remaps["Webrtc"] = "WebRTC"; + capitalize_string_remaps["Websocket"] = "WebSocket"; +} + +EditorPropertyNameProcessor::~EditorPropertyNameProcessor() { + singleton = nullptr; +} diff --git a/editor/editor_property_name_processor.h b/editor/editor_property_name_processor.h new file mode 100644 index 0000000000..efd7abced3 --- /dev/null +++ b/editor/editor_property_name_processor.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* editor_property_name_processor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef EDITOR_PROPERTY_NAME_PROCESSOR_H +#define EDITOR_PROPERTY_NAME_PROCESSOR_H + +#include "scene/main/node.h" + +class EditorPropertyNameProcessor : public Node { + GDCLASS(EditorPropertyNameProcessor, Node); + + static EditorPropertyNameProcessor *singleton; + + Map<String, String> capitalize_string_remaps; + + String _capitalize_name(const String &p_name) const; + +public: + static EditorPropertyNameProcessor *get_singleton() { return singleton; } + + // Capitalize & localize property path segments. + String process_name(const String &p_name) const; + + // Make tooltip string for names processed by process_name(). + String make_tooltip_for_name(const String &p_name) const; + + EditorPropertyNameProcessor(); + ~EditorPropertyNameProcessor(); +}; + +#endif // EDITOR_PROPERTY_NAME_PROCESSOR_H diff --git a/editor/editor_sectioned_inspector.cpp b/editor/editor_sectioned_inspector.cpp index 19374f826a..a44cf20771 100644 --- a/editor/editor_sectioned_inspector.cpp +++ b/editor/editor_sectioned_inspector.cpp @@ -30,6 +30,7 @@ #include "editor_sectioned_inspector.h" +#include "editor/editor_property_name_processor.h" #include "editor/editor_scale.h" class SectionedInspectorFilter : public Object { @@ -259,7 +260,8 @@ void SectionedInspector::update_category_list() { if (!section_map.has(metasection)) { TreeItem *ms = sections->create_item(parent); section_map[metasection] = ms; - ms->set_text(0, sectionarr[i].capitalize()); + ms->set_text(0, EditorPropertyNameProcessor::get_singleton()->process_name(sectionarr[i])); + ms->set_tooltip(0, EditorPropertyNameProcessor::get_singleton()->make_tooltip_for_name(sectionarr[i])); ms->set_metadata(0, metasection); ms->set_selectable(0, false); } diff --git a/editor/editor_settings_dialog.cpp b/editor/editor_settings_dialog.cpp index c0113e281e..1ec6d4a3a6 100644 --- a/editor/editor_settings_dialog.cpp +++ b/editor/editor_settings_dialog.cpp @@ -37,6 +37,7 @@ #include "editor/editor_file_system.h" #include "editor/editor_log.h" #include "editor/editor_node.h" +#include "editor/editor_property_name_processor.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "scene/gui/margin_container.h" @@ -430,8 +431,9 @@ void EditorSettingsDialog::_update_shortcuts() { } else { section = shortcuts->create_item(root); - String item_name = section_name.capitalize(); + String item_name = EditorPropertyNameProcessor::get_singleton()->process_name(section_name); section->set_text(0, item_name); + section->set_tooltip(0, EditorPropertyNameProcessor::get_singleton()->make_tooltip_for_name(section_name)); section->set_selectable(0, false); section->set_selectable(1, false); section->set_custom_bg_color(0, shortcuts->get_theme_color(SNAME("prop_subsection"), SNAME("Editor"))); diff --git a/editor/translations/extract.py b/editor/translations/extract.py index 2594629e5c..65d1341e9e 100755 --- a/editor/translations/extract.py +++ b/editor/translations/extract.py @@ -2,6 +2,7 @@ import fnmatch import os +import re import shutil import subprocess import sys @@ -31,6 +32,15 @@ for root, dirnames, filenames in os.walk("."): matches.sort() +remaps = {} +remap_re = re.compile(r'capitalize_string_remaps\["(.+)"\] = "(.+)";') +with open("editor/editor_property_name_processor.cpp") as f: + for line in f: + m = remap_re.search(line) + if m: + remaps[m.group(1)] = m.group(2) + + unique_str = [] unique_loc = {} ctx_group = {} # Store msgctx, msg, and locations. @@ -53,6 +63,43 @@ msgstr "" """ +# Regex "(?P<name>(?:[^"\\]|\\.)*)" creates a group named `name` that matches a string. +message_patterns = { + re.compile(r'RTR\("(?P<message>(?:[^"\\]|\\.)*)"(?:, "(?P<context>(?:[^"\\]|\\.)*)")?\)'): False, + re.compile(r'TTR\("(?P<message>(?:[^"\\]|\\.)*)"(?:, "(?P<context>(?:[^"\\]|\\.)*)")?\)'): False, + re.compile(r'TTRC\("(?P<message>(?:[^"\\]|\\.)*)"\)'): False, + re.compile( + r'TTRN\("(?P<message>(?:[^"\\]|\\.)*)", "(?P<plural_message>(?:[^"\\]|\\.)*)",[^,)]+?(?:, "(?P<context>(?:[^"\\]|\\.)*)")?\)' + ): False, + re.compile( + r'RTRN\("(?P<message>(?:[^"\\]|\\.)*)", "(?P<plural_message>(?:[^"\\]|\\.)*)",[^,)]+?(?:, "(?P<context>(?:[^"\\]|\\.)*)")?\)' + ): False, + re.compile(r'_initial_set\("(?P<message>[^"]+?)",'): True, + re.compile(r'GLOBAL_DEF(?:_RST)?\("(?P<message>[^".]+?)",'): True, + re.compile(r'EDITOR_DEF(?:_RST)?\("(?P<message>[^"]+?)",'): True, + re.compile(r'ADD_PROPERTY\(PropertyInfo\(Variant::[A-Z]+,\s*"(?P<message>[^"]+?)",'): True, + re.compile(r'ADD_GROUP\("(?P<message>[^"]+?)",'): False, +} + + +# See String::camelcase_to_underscore(). +capitalize_re = re.compile(r"(?<=\D)(?=\d)|(?<=\d)(?=\D([a-z]|\d))") + + +def _process_editor_string(name): + # See String::capitalize(). + # fmt: off + capitalized = " ".join( + part.title() + for part in capitalize_re.sub("_", name).replace("_", " ").split() + ) + # fmt: on + # See EditorStringProcessor::process_string(). + for key, value in remaps.items(): + capitalized = capitalized.replace(key, value) + return capitalized + + def _write_message(msgctx, msg, msg_plural, location): global main_po main_po += "#: " + location + "\n" @@ -180,11 +227,6 @@ def _extract_translator_comment(line, is_block_translator_comment): def process_file(f, fname): - - global main_po, unique_str, unique_loc - - patterns = ['RTR("', 'TTR("', 'TTRC("', 'TTRN("', 'RTRN("'] - l = f.readline() lc = 1 reading_translator_comment = False @@ -207,86 +249,58 @@ def process_file(f, fname): if not reading_translator_comment: translator_comment = translator_comment[:-1] # Remove extra \n at the end. - idx = 0 - pos = 0 - - while not reading_translator_comment and pos >= 0: - # Loop until a pattern is found. If not, next line. - pos = l.find(patterns[idx], pos) - if pos == -1: - if idx < len(patterns) - 1: - idx += 1 - pos = 0 - continue - pos += len(patterns[idx]) - - # Read msg until " - msg = "" - while pos < len(l) and (l[pos] != '"' or l[pos - 1] == "\\"): - msg += l[pos] - pos += 1 - - # Read plural. - msg_plural = "" - if patterns[idx] in ['TTRN("', 'RTRN("']: - pos = l.find('"', pos + 1) - pos += 1 - while pos < len(l) and (l[pos] != '"' or l[pos - 1] == "\\"): - msg_plural += l[pos] - pos += 1 - - # Read context. - msgctx = "" - pos += 1 - read_ctx = False - while pos < len(l): - if l[pos] == ")": - break - elif l[pos] == '"': - read_ctx = True - break - pos += 1 - - pos += 1 - if read_ctx: - while pos < len(l) and (l[pos] != '"' or l[pos - 1] == "\\"): - msgctx += l[pos] - pos += 1 - - # File location. - location = os.path.relpath(fname).replace("\\", "/") - if line_nb: - location += ":" + str(lc) - - # Write translator comment. - _write_translator_comment(msgctx, msg, translator_comment) + if not reading_translator_comment: + for pattern, is_property_path in message_patterns.items(): + for m in pattern.finditer(l): + location = os.path.relpath(fname).replace("\\", "/") + if line_nb: + location += ":" + str(lc) + + groups = m.groupdict("") + msg = groups.get("message", "") + msg_plural = groups.get("plural_message", "") + msgctx = groups.get("context", "") + + if is_property_path: + for part in msg.split("/"): + _add_message(_process_editor_string(part), msg_plural, msgctx, location, translator_comment) + else: + _add_message(msg, msg_plural, msgctx, location, translator_comment) translator_comment = "" - if msgctx != "": - # If it's a new context or a new message within an existing context, then write new msgid. - # Else add location to existing msgid. - if not msgctx in ctx_group: - _write_message(msgctx, msg, msg_plural, location) - ctx_group[msgctx] = {msg: [location]} - elif not msg in ctx_group[msgctx]: - _write_message(msgctx, msg, msg_plural, location) - ctx_group[msgctx][msg] = [location] - elif not location in ctx_group[msgctx][msg]: - _add_additional_location(msgctx, msg, location) - ctx_group[msgctx][msg].append(location) - else: - if not msg in unique_str: - _write_message(msgctx, msg, msg_plural, location) - unique_str.append(msg) - unique_loc[msg] = [location] - elif not location in unique_loc[msg]: - _add_additional_location(msgctx, msg, location) - unique_loc[msg].append(location) - l = f.readline() lc += 1 +def _add_message(msg, msg_plural, msgctx, location, translator_comment): + global main_po, unique_str, unique_loc + + # Write translator comment. + _write_translator_comment(msgctx, msg, translator_comment) + translator_comment = "" + + if msgctx != "": + # If it's a new context or a new message within an existing context, then write new msgid. + # Else add location to existing msgid. + if not msgctx in ctx_group: + _write_message(msgctx, msg, msg_plural, location) + ctx_group[msgctx] = {msg: [location]} + elif not msg in ctx_group[msgctx]: + _write_message(msgctx, msg, msg_plural, location) + ctx_group[msgctx][msg] = [location] + elif not location in ctx_group[msgctx][msg]: + _add_additional_location(msgctx, msg, location) + ctx_group[msgctx][msg].append(location) + else: + if not msg in unique_str: + _write_message(msgctx, msg, msg_plural, location) + unique_str.append(msg) + unique_loc[msg] = [location] + elif not location in unique_loc[msg]: + _add_additional_location(msgctx, msg, location) + unique_loc[msg].append(location) + + print("Updating the editor.pot template...") for fname in matches: |