diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp index 30b33d82b22..88f5224a667 100644 --- a/editor/editor_feature_profile.cpp +++ b/editor/editor_feature_profile.cpp @@ -33,6 +33,7 @@ #include "core/os/dir_access.h" #include "editor/editor_settings.h" #include "editor_node.h" +#include "editor_property_name_processor.h" #include "editor_scale.h" const char *EditorFeatureProfile::feature_names[FEATURE_MAX] = { @@ -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->get().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 50c4739a0fb..6059282a386 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -36,6 +36,7 @@ #include "dictionary_property_edit.h" #include "editor_feature_profile.h" #include "editor_node.h" +#include "editor_property_name_processor.h" #include "editor_scale.h" #include "editor_settings.h" #include "multi_node_edit.h" @@ -1551,11 +1552,11 @@ void EditorInspector::update_tree() { if (dot != -1) { String ov = name.right(dot); name = name.substr(0, dot); - name = name.capitalize(); + name = EditorPropertyNameProcessor::get_singleton()->process_name(name); name += ov; } else { - name = name.capitalize(); + name = EditorPropertyNameProcessor::get_singleton()->process_name(name); } } @@ -1565,7 +1566,7 @@ void EditorInspector::update_tree() { String cat = path; if (capitalize_paths) { - cat = cat.capitalize(); + cat = EditorPropertyNameProcessor::get_singleton()->process_name(cat); } if (!filter.is_subsequence_ofi(cat) && !filter.is_subsequence_ofi(name) && property_prefix.to_lower().find(filter.to_lower()) == -1) { @@ -1594,13 +1595,15 @@ void EditorInspector::update_tree() { current_vbox->add_child(section); sections.push_back(section); + String label = path_name; if (capitalize_paths) { - path_name = path_name.capitalize(); + label = EditorPropertyNameProcessor::get_singleton()->process_name(label); } Color c = sscolor; c.a /= level; - section->setup(acc_path, path_name, object, c, use_folding); + section->setup(acc_path, label, object, c, use_folding); + section->set_tooltip(EditorPropertyNameProcessor::get_singleton()->make_tooltip_for_name(path_name)); VBoxContainer *vb = section->get_vbox(); item_path[acc_path] = vb; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index d5486234322..b0e9ed77be7 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -80,6 +80,7 @@ #include "editor/editor_log.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_native.h" @@ -5771,6 +5772,9 @@ int EditorNode::execute_and_show_output(const String &p_title, const String &p_p } 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; @@ -5970,6 +5974,7 @@ EditorNode::EditorNode() { EDITOR_DEF("interface/editor/show_update_spinner", false); EDITOR_DEF("interface/editor/update_continuously", false); EDITOR_DEF("interface/editor/update_vital_only", false); + EDITOR_DEF("interface/editor/translate_properties", true); EDITOR_DEF_RST("interface/scene_tabs/restore_scenes_on_load", false); EDITOR_DEF_RST("interface/scene_tabs/show_thumbnail_on_hover", true); EDITOR_DEF_RST("interface/inspector/capitalize_properties", true); diff --git a/editor/editor_property_name_processor.cpp b/editor/editor_property_name_processor.cpp new file mode 100644 index 00000000000..38003ab2f4a --- /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::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 00000000000..efd7abced3b --- /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 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 e271e586ded..7a211bc331e 100644 --- a/editor/editor_sectioned_inspector.cpp +++ b/editor/editor_sectioned_inspector.cpp @@ -29,7 +29,10 @@ /*************************************************************************/ #include "editor_sectioned_inspector.h" + +#include "editor_property_name_processor.h" #include "editor_scale.h" + class SectionedInspectorFilter : public Object { GDCLASS(SectionedInspectorFilter, Object); @@ -266,7 +269,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/settings_config_dialog.cpp b/editor/settings_config_dialog.cpp index 616d16a7060..c6a1b14ba49 100644 --- a/editor/settings_config_dialog.cpp +++ b/editor/settings_config_dialog.cpp @@ -35,6 +35,7 @@ #include "editor_file_system.h" #include "editor_log.h" #include "editor_node.h" +#include "editor_property_name_processor.h" #include "editor_scale.h" #include "editor_settings.h" #include "scene/gui/margin_container.h" @@ -217,8 +218,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)); if (collapsed.has(item_name)) { section->set_collapsed(collapsed[item_name]); diff --git a/editor/translations/extract.py b/editor/translations/extract.py index ed225430e64..ac94fbcb3a4 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 = {} main_po = """ @@ -52,6 +62,36 @@ msgstr "" """ +message_patterns = { + re.compile(r'RTR\("(([^"\\]|\\.)*)"\)'): False, + re.compile(r'TTR\("(([^"\\]|\\.)*)"\)'): False, + re.compile(r'TTRC\("(([^"\\]|\\.)*)"\)'): False, + re.compile(r'_initial_set\("([^"]+?)",'): True, + re.compile(r'GLOBAL_DEF(?:_RST)?\("([^".]+?)",'): True, + re.compile(r'EDITOR_DEF(?:_RST)?\("([^"]+?)",'): True, + re.compile(r'ADD_PROPERTY\(PropertyInfo\(Variant::[A-Z]+,\s*"([^"]+?)",'): True, + re.compile(r'ADD_GROUP\("([^"]+?)",'): 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_translator_comment(msg, translator_comment): if translator_comment == "": return @@ -149,11 +189,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("'] - l = f.readline() lc = 1 reading_translator_comment = False @@ -176,49 +211,48 @@ 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 + 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) - while not reading_translator_comment and pos >= 0: - pos = l.find(patterns[idx], pos) - if pos == -1: - if idx < len(patterns) - 1: - idx += 1 - pos = 0 - continue - pos += len(patterns[idx]) + msg = m.group(1) - msg = "" - while pos < len(l) and (l[pos] != '"' or l[pos - 1] == "\\"): - msg += l[pos] - pos += 1 + if is_property_path: + for part in msg.split("/"): + _add_message(_process_editor_string(part), location, translator_comment) + else: + _add_message(msg, location, translator_comment) - location = os.path.relpath(fname).replace("\\", "/") - if line_nb: - location += ":" + str(lc) - - # Write translator comment. - _write_translator_comment(msg, translator_comment) translator_comment = "" - if not msg in unique_str: - main_po += "#: " + location + "\n" - main_po += 'msgid "' + msg + '"\n' - main_po += 'msgstr ""\n\n' - unique_str.append(msg) - unique_loc[msg] = [location] - elif not location in unique_loc[msg]: - # Add additional location to previous occurrence too - msg_pos = main_po.find('\nmsgid "' + msg + '"') - if msg_pos == -1: - print("Someone apparently thought writing Python was as easy as GDScript. Ping Akien.") - main_po = main_po[:msg_pos] + " " + location + main_po[msg_pos:] - unique_loc[msg].append(location) - l = f.readline() lc += 1 +def _add_message(msg, location, translator_comment): + global main_po, unique_str, unique_loc + + # Write translator comment. + _write_translator_comment(msg, translator_comment) + + if not msg in unique_str: + main_po += "#: " + location + "\n" + main_po += 'msgid "' + msg + '"\n' + main_po += 'msgstr ""\n\n' + unique_str.append(msg) + unique_loc[msg] = [location] + elif not location in unique_loc[msg]: + # Add additional location to previous occurrence too + msg_pos = main_po.find('\nmsgid "' + msg + '"') + if msg_pos == -1: + print("Someone apparently thought writing Python was as easy as GDScript. Ping Akien.") + main_po = main_po[:msg_pos] + " " + location + main_po[msg_pos:] + unique_loc[msg].append(location) + + print("Updating the editor.pot template...") for fname in matches: