diff --git a/addons/zylann.translation_editor/tools/extractor.gd b/addons/zylann.translation_editor/tools/extractor.gd index 8774865..b0ae677 100644 --- a/addons/zylann.translation_editor/tools/extractor.gd +++ b/addons/zylann.translation_editor/tools/extractor.gd @@ -16,27 +16,31 @@ var _time_before := 0.0 var _ignored_paths := {} var _paths := [] var _logger = Logger.get_for(self) +var _prefix := "" +const _prefix_exclusive := true -func extract_async(root: String, ignored_paths := []): - _prepare(root, ignored_paths) +func extract_async(root: String, ignored_paths := [], prefix := ""): + _prepare(root, ignored_paths, prefix) _thread = Thread.new() _thread.start(self, "_extract_thread_func", root) -func extract(root: String, ignored_paths := []) -> Dictionary: - _prepare(root, ignored_paths) +func extract(root: String, ignored_paths := [], prefix := "") -> Dictionary: + _prepare(root, ignored_paths, prefix) _extract(root) return _strings -func _prepare(root: String, ignored_paths: Array): +func _prepare(root: String, ignored_paths: Array, prefix: String): _time_before = OS.get_ticks_msec() assert(_thread == null) _ignored_paths.clear() for p in ignored_paths: _ignored_paths[root.plus_file(p)] = true + + _prefix = prefix _strings.clear() @@ -57,6 +61,10 @@ func _extract(root: String): _process_tscn(f, fpath) "gd": _process_gd(f, fpath) + "json": + _process_quoted_text_generic(f, fpath) + "cs": + _process_quoted_text_generic(f, fpath) f.close() call_deferred("_report_progress", float(i) / float(len(_paths))) @@ -95,12 +103,19 @@ func _index_file(fpath: String): func _process_tscn(f: File, fpath: String): - # TOOD Also search for "window_title" and "dialog_text" var patterns := [ "text =", "window_title =", - "dialog_text =" + "dialog_text =", ] + + if _prefix != "": + var p = str("\"", _prefix) + if _prefix_exclusive: + patterns = [p] + else: + patterns.append(p) + var text := "" var state := STATE_SEARCHING var line_number := 0 @@ -115,22 +130,29 @@ func _process_tscn(f: File, fpath: String): match state: STATE_SEARCHING: var pattern : String - var i : int + var pattern_begin_index : int = -1 + for p in patterns: - i = line.find(p) - if i != -1: + var i := line.find(p) + if i != -1 and (i < pattern_begin_index or pattern_begin_index == -1): + pattern_begin_index = i pattern = p - break - if i == -1: - continue - - var begin_quote_index := line.find('"', i + len(pattern)) - if begin_quote_index == -1: - _logger.error( - "Could not find begin quote after text property, in {0}, line {1}" \ - .format([fpath, line_number])) + if pattern_begin_index == -1: continue + + var begin_quote_index := -1 + + if pattern[0] == "\"": + begin_quote_index = pattern_begin_index + + else: + begin_quote_index = line.find('"', pattern_begin_index + len(pattern)) + if begin_quote_index == -1: + _logger.error( + "Could not find begin quote after text property, in {0}, line {1}" \ + .format([fpath, line_number])) + continue var end_quote_index := line.rfind('"') @@ -139,7 +161,7 @@ func _process_tscn(f: File, fpath: String): text = line.substr(begin_quote_index + 1, end_quote_index - begin_quote_index - 1) - if text != "": + if text != "" and text != _prefix: _add_string(fpath, line_number, text) text = "" @@ -163,6 +185,18 @@ func _process_gd(f: File, fpath: String): var text := "" var line_number := 0 + var patterns := [ + "tr(", + "TranslationServer.translate(" + ] + + if _prefix != "": + var p = str("\"", _prefix) + if _prefix_exclusive: + patterns = [p] + else: + patterns.append(p) + while not f.eof_reached(): var line := f.get_line().strip_edges() line_number += 1 @@ -173,49 +207,92 @@ func _process_gd(f: File, fpath: String): # Search for one or multiple tr("...") in the same line var search_index := 0 var counter := 0 - while true: - var pattern := "tr(" - var call_index := line.find(pattern, search_index) - if call_index == -1: - pattern = "TranslationServer.translate(" - call_index = line.find(pattern, search_index) - if call_index == -1: - break + while search_index < len(line): + # Find closest pattern + var pattern : String + var pattern_start_index := -1 + for p in patterns: + var i = line.find(p, search_index) + if i != -1 and (i < pattern_start_index or pattern_start_index == -1): + pattern_start_index = i + pattern = p + + if pattern_start_index == -1: + # No pattern found in entire line + break + + var begin_quote_index = -1 + if pattern[0] == "\"": + # Detected by prefix + begin_quote_index = pattern_start_index - if call_index != 0: - if line.substr(call_index - 1, 3).is_valid_identifier(): - # not a tr( call, skip - search_index = call_index + len(pattern) + else: + # Detect by call to TranslationServer + if line.substr(pattern_start_index - 1, 3).is_valid_identifier() \ + or line[pattern_start_index - 1] == '"': + # not a tr( call, or inside a string. skip + search_index = pattern_start_index + len(pattern) continue - if line[call_index - 1] == '"': - break # TODO There may be more cases to handle # They may need regexes or a simplified GDScript parser to extract properly - var begin_quote_index := line.find('"', call_index) - if begin_quote_index == -1: - # Multiline or procedural strings not supported - _logger.error("Begin quote not found in {0}, line {1}".format([fpath, line_number])) - break + begin_quote_index = line.find('"', pattern_start_index) + if begin_quote_index == -1: + # Multiline or procedural strings not supported + _logger.error("Begin quote not found in {0}, line {1}" \ + .format([fpath, line_number])) + # No quote found in entire line, skip + break + var end_quote_index := find_unescaped_quote(line, begin_quote_index + 1) if end_quote_index == -1: # Multiline or procedural strings not supported _logger.error("End quote not found in {0}, line {1}".format([fpath, line_number])) break + text = line.substr(begin_quote_index + 1, end_quote_index - begin_quote_index - 1) - var end_bracket_index := line.find(')', end_quote_index) - if end_bracket_index == -1: - # Multiline or procedural strings not supported - _logger.error("End bracket not found in {0}, line {1}".format([fpath, line_number])) - break - _add_string(fpath, line_number, text) - search_index = end_bracket_index +# var end_bracket_index := line.find(')', end_quote_index) +# if end_bracket_index == -1: +# # Multiline or procedural strings not supported +# _logger.error("End bracket not found in {0}, line {1}".format([fpath, line_number])) +# break + + if text != "" and text != _prefix: + _add_string(fpath, line_number, text) +# search_index = end_bracket_index + search_index = end_quote_index + 1 counter += 1 # If that fails it means we spent 100 iterations in the same line, that's suspicious assert(counter < 100) +func _process_quoted_text_generic(f: File, fpath: String): + var pattern := str("\"", _prefix) + var line_number := 0 + + while not f.eof_reached(): + var line := f.get_line().strip_edges() + line_number += 1 + + var search_index := 0 + while search_index < len(line): + var i := line.find(pattern, search_index) + if i == -1: + break + + var begin_quote_index := i + var end_quote_index := find_unescaped_quote(line, begin_quote_index + 1) + if end_quote_index == -1: + break + + var text := line.substr(begin_quote_index + 1, end_quote_index - begin_quote_index - 1) + if text != "" and text != _prefix: + _add_string(fpath, line_number, text) + + search_index = end_quote_index + 1 + + static func find_unescaped_quote(s, from) -> int: while true: var i = s.find('"', from) diff --git a/addons/zylann.translation_editor/tools/extractor_dialog.gd b/addons/zylann.translation_editor/tools/extractor_dialog.gd index 31e2edc..220c8d9 100644 --- a/addons/zylann.translation_editor/tools/extractor_dialog.gd +++ b/addons/zylann.translation_editor/tools/extractor_dialog.gd @@ -6,13 +6,14 @@ const Logger = preload("./util/logger.gd") signal import_selected(strings) -onready var _root_path_edit : LineEdit = $VBoxContainer/HBoxContainer/RootPathEdit -onready var _excluded_dirs_edit : LineEdit = $VBoxContainer/Options/ExcludedDirsEdit -onready var _summary_label : Label = $VBoxContainer/StatusBar/SummaryLabel -onready var _results_list : Tree = $VBoxContainer/Results -onready var _progress_bar : ProgressBar = $VBoxContainer/StatusBar/ProgressBar -onready var _extract_button : Button = $VBoxContainer/Buttons/ExtractButton -onready var _import_button : Button = $VBoxContainer/Buttons/ImportButton +onready var _root_path_edit : LineEdit = $VB/HB/RootPathEdit +onready var _excluded_dirs_edit : LineEdit = $VB/HB2/ExcludedDirsEdit +onready var _prefix_edit : LineEdit = $VB/HB3/PrefixLineEdit +onready var _summary_label : Label = $VB/StatusBar/SummaryLabel +onready var _results_list : Tree = $VB/Results +onready var _progress_bar : ProgressBar = $VB/StatusBar/ProgressBar +onready var _extract_button : Button = $VB/Buttons/ExtractButton +onready var _import_button : Button = $VB/Buttons/ImportButton var _extractor : Extractor = null # { string => { fpath => line number } } @@ -57,10 +58,12 @@ func _on_ExtractButton_pressed(): for i in len(excluded_dirs): excluded_dirs[i] = excluded_dirs[i].strip_edges() + var prefix := _prefix_edit.text.strip_edges() + _extractor = Extractor.new() _extractor.connect("progress_reported", self, "_on_Extractor_progress_reported") _extractor.connect("finished", self, "_on_Extractor_finished") - _extractor.extract_async(root, excluded_dirs) + _extractor.extract_async(root, excluded_dirs, prefix) _progress_bar.value = 0 _progress_bar.show() diff --git a/addons/zylann.translation_editor/tools/extractor_dialog.tscn b/addons/zylann.translation_editor/tools/extractor_dialog.tscn index 29273d7..b8b3c3e 100644 --- a/addons/zylann.translation_editor/tools/extractor_dialog.tscn +++ b/addons/zylann.translation_editor/tools/extractor_dialog.tscn @@ -3,6 +3,7 @@ [ext_resource path="res://addons/zylann.translation_editor/tools/extractor_dialog.gd" type="Script" id=1] [node name="ExtractorDialog" type="WindowDialog"] +visible = true margin_left = 54.0 margin_top = 68.0 margin_right = 694.0 @@ -11,44 +12,50 @@ rect_min_size = Vector2( 640, 480 ) window_title = "String extractor" resizable = true script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} -[node name="VBoxContainer" type="VBoxContainer" parent="."] +[node name="VB" type="VBoxContainer" parent="."] anchor_right = 1.0 anchor_bottom = 1.0 margin_left = 8.0 margin_top = 8.0 margin_right = -8.0 margin_bottom = -8.0 +__meta__ = { +"_edit_use_anchors_": false +} -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +[node name="HB" type="HBoxContainer" parent="VB"] margin_right = 624.0 margin_bottom = 24.0 -[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"] +[node name="Label" type="Label" parent="VB/HB"] margin_top = 5.0 margin_right = 29.0 margin_bottom = 19.0 text = "Root" -[node name="RootPathEdit" type="LineEdit" parent="VBoxContainer/HBoxContainer"] +[node name="RootPathEdit" type="LineEdit" parent="VB/HB"] margin_left = 33.0 margin_right = 624.0 margin_bottom = 24.0 size_flags_horizontal = 3 text = "res://" -[node name="Options" type="HBoxContainer" parent="VBoxContainer"] +[node name="HB2" type="HBoxContainer" parent="VB"] margin_top = 28.0 margin_right = 624.0 margin_bottom = 52.0 -[node name="ExcludedDirsLabel" type="Label" parent="VBoxContainer/Options"] +[node name="ExcludedDirsLabel" type="Label" parent="VB/HB2"] margin_top = 5.0 margin_right = 122.0 margin_bottom = 19.0 text = "Ignored directories" -[node name="ExcludedDirsEdit" type="LineEdit" parent="VBoxContainer/Options"] +[node name="ExcludedDirsEdit" type="LineEdit" parent="VB/HB2"] margin_left = 126.0 margin_right = 624.0 margin_bottom = 24.0 @@ -56,69 +63,89 @@ hint_tooltip = "Directories seperated by semicolons `;`" size_flags_horizontal = 3 text = "addons" -[node name="StatusBar" type="Control" parent="VBoxContainer"] +[node name="HB3" type="HBoxContainer" parent="VB"] margin_top = 56.0 margin_right = 624.0 margin_bottom = 80.0 + +[node name="Label" type="Label" parent="VB/HB3"] +margin_top = 5.0 +margin_right = 36.0 +margin_bottom = 19.0 +text = "Prefix" + +[node name="PrefixLineEdit" type="LineEdit" parent="VB/HB3"] +margin_left = 40.0 +margin_right = 624.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 + +[node name="StatusBar" type="Control" parent="VB"] +margin_top = 84.0 +margin_right = 624.0 +margin_bottom = 108.0 rect_min_size = Vector2( 0, 24 ) -[node name="SummaryLabel" type="Label" parent="VBoxContainer/StatusBar"] +[node name="SummaryLabel" type="Label" parent="VB/StatusBar"] margin_top = 4.0 margin_right = 624.0 margin_bottom = 18.0 align = 1 +__meta__ = { +"_edit_use_anchors_": false +} -[node name="ProgressBar" type="ProgressBar" parent="VBoxContainer/StatusBar"] +[node name="ProgressBar" type="ProgressBar" parent="VB/StatusBar"] visible = false margin_right = 624.0 margin_bottom = 16.0 step = 1.0 -[node name="Results" type="Tree" parent="VBoxContainer"] -margin_top = 84.0 +[node name="Results" type="Tree" parent="VB"] +margin_top = 112.0 margin_right = 624.0 margin_bottom = 416.0 size_flags_vertical = 3 hide_root = true select_mode = 1 -[node name="Spacer" type="Control" parent="VBoxContainer"] +[node name="Spacer" type="Control" parent="VB"] margin_top = 420.0 margin_right = 624.0 margin_bottom = 428.0 rect_min_size = Vector2( 0, 8 ) -[node name="Buttons" type="HBoxContainer" parent="VBoxContainer"] +[node name="Buttons" type="HBoxContainer" parent="VB"] margin_top = 432.0 margin_right = 624.0 margin_bottom = 452.0 custom_constants/separation = 16 alignment = 1 -[node name="ExtractButton" type="Button" parent="VBoxContainer/Buttons"] +[node name="ExtractButton" type="Button" parent="VB/Buttons"] margin_left = 184.0 margin_right = 239.0 margin_bottom = 20.0 text = "Extract" -[node name="ImportButton" type="Button" parent="VBoxContainer/Buttons"] +[node name="ImportButton" type="Button" parent="VB/Buttons"] margin_left = 255.0 margin_right = 370.0 margin_bottom = 20.0 disabled = true text = "Import selected" -[node name="CancelButton" type="Button" parent="VBoxContainer/Buttons"] +[node name="CancelButton" type="Button" parent="VB/Buttons"] margin_left = 386.0 margin_right = 440.0 margin_bottom = 20.0 text = "Cancel" -[node name="Spacer2" type="Control" parent="VBoxContainer"] +[node name="Spacer2" type="Control" parent="VB"] margin_top = 456.0 margin_right = 624.0 margin_bottom = 464.0 rect_min_size = Vector2( 0, 8 ) -[connection signal="pressed" from="VBoxContainer/Buttons/ExtractButton" to="." method="_on_ExtractButton_pressed"] -[connection signal="pressed" from="VBoxContainer/Buttons/ImportButton" to="." method="_on_ImportButton_pressed"] -[connection signal="pressed" from="VBoxContainer/Buttons/CancelButton" to="." method="_on_CancelButton_pressed"] +[connection signal="pressed" from="VB/Buttons/ExtractButton" to="." method="_on_ExtractButton_pressed"] +[connection signal="pressed" from="VB/Buttons/ImportButton" to="." method="_on_ImportButton_pressed"] +[connection signal="pressed" from="VB/Buttons/CancelButton" to="." method="_on_CancelButton_pressed"]