Added support for string prefix and json files

This commit is contained in:
Marc Gilleron 2020-06-15 20:51:04 +01:00
parent 9bb25b67e9
commit c01422d4b7
3 changed files with 182 additions and 75 deletions

View File

@ -16,21 +16,23 @@ 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)
@ -38,6 +40,8 @@ func _prepare(root: String, ignored_paths: Array):
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:
if pattern_begin_index == -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]))
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 call_index != 0:
if line.substr(call_index - 1, 3).is_valid_identifier():
# not a tr( call, skip
search_index = call_index + len(pattern)
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
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)

View File

@ -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()

View File

@ -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"]