mirror of
https://github.com/Relintai/godot_translation_editor.git
synced 2025-02-10 14:20:09 +01:00
Added strings extractor, fix dialogs not freed on plugin disable
This commit is contained in:
parent
f99df8d186
commit
126acb0247
193
addons/zylann.translation_editor/tools/extractor.gd
Normal file
193
addons/zylann.translation_editor/tools/extractor.gd
Normal file
@ -0,0 +1,193 @@
|
||||
|
||||
const STATE_SEARCHING = 0
|
||||
const STATE_READING_TEXT = 1
|
||||
|
||||
signal finished(results)
|
||||
|
||||
var _strings = {}
|
||||
var _thread = null
|
||||
var _time_before = 0.0
|
||||
var _ignored_paths = {}
|
||||
|
||||
|
||||
func extract(root, ignored_paths=[]):
|
||||
_time_before = OS.get_ticks_msec()
|
||||
assert(_thread == null)
|
||||
|
||||
_ignored_paths.clear()
|
||||
for p in ignored_paths:
|
||||
_ignored_paths[root.plus_file(p)] = true
|
||||
|
||||
_thread = Thread.new()
|
||||
_thread.start(self, "_extract", root)
|
||||
|
||||
|
||||
func _extract(root):
|
||||
_walk(root, funcref(self, "_process_file"), funcref(self, "_filter"))
|
||||
call_deferred("_finished")
|
||||
|
||||
|
||||
func _finished():
|
||||
_thread.wait_to_finish()
|
||||
_thread = null
|
||||
var elapsed = float(OS.get_ticks_msec() - _time_before) / 1000.0
|
||||
print("Extraction took ", elapsed, " seconds")
|
||||
emit_signal("finished", _strings)
|
||||
|
||||
|
||||
func _filter(path):
|
||||
if path in _ignored_paths:
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func _process_file(fpath):
|
||||
var ext = fpath.get_extension()
|
||||
#print("File ", fpath)
|
||||
|
||||
if ext != "tscn" and ext != "gd":
|
||||
return
|
||||
|
||||
var f = File.new()
|
||||
var err = f.open(fpath, File.READ)
|
||||
if err != OK:
|
||||
printerr("Could not open '", fpath, "', for read, error ", err)
|
||||
return
|
||||
|
||||
match ext:
|
||||
"tscn":
|
||||
_process_tscn(f, fpath)
|
||||
"gd":
|
||||
_process_gd(f, fpath)
|
||||
|
||||
|
||||
func _process_tscn(f, fpath):
|
||||
var pattern = "text ="
|
||||
var text = ""
|
||||
var state = STATE_SEARCHING
|
||||
var line_number = 0
|
||||
|
||||
while not f.eof_reached():
|
||||
var line = f.get_line()
|
||||
line_number += 1
|
||||
|
||||
if line == "":
|
||||
continue
|
||||
|
||||
match state:
|
||||
|
||||
STATE_SEARCHING:
|
||||
var i = line.find(pattern)
|
||||
if i != -1:
|
||||
var begin_quote_index = line.find('"', i + len(pattern))
|
||||
if begin_quote_index == -1:
|
||||
printerr("Could not find begin quote after text property, in ", fpath, " line ", line_number)
|
||||
continue
|
||||
var end_quote_index = line.rfind('"')
|
||||
if end_quote_index != -1 and end_quote_index > begin_quote_index and line[end_quote_index - 1] != '\\':
|
||||
text = line.substr(begin_quote_index + 1, end_quote_index - begin_quote_index - 1)
|
||||
if text != "":
|
||||
_add_string(fpath, line_number, text)
|
||||
text = ""
|
||||
else:
|
||||
# The text may be multiline
|
||||
text = str(line.right(begin_quote_index + 1), "\n")
|
||||
state = STATE_READING_TEXT
|
||||
|
||||
STATE_READING_TEXT:
|
||||
var end_quote_index = line.rfind('"')
|
||||
if end_quote_index != -1 and line[end_quote_index - 1] != '\\':
|
||||
text = str(text, line.left(end_quote_index))
|
||||
_add_string(fpath, line_number, text)
|
||||
text = ""
|
||||
state = STATE_SEARCHING
|
||||
else:
|
||||
text = str(text, line, "\n")
|
||||
|
||||
|
||||
func _process_gd(f, fpath):
|
||||
var pattern = "tr("
|
||||
var text = ""
|
||||
var line_number = 0
|
||||
|
||||
while not f.eof_reached():
|
||||
var line = f.get_line().strip_edges()
|
||||
line_number += 1
|
||||
|
||||
if line == "" or line[0] == "#":
|
||||
continue
|
||||
|
||||
# Search for one or multiple tr("...") in the same line
|
||||
var search_index = 0
|
||||
var counter = 0
|
||||
while true:
|
||||
var call_index = line.find(pattern, search_index)
|
||||
if call_index == -1:
|
||||
break
|
||||
if call_index != 0:
|
||||
if line.substr(call_index - 1, 3).is_valid_identifier():
|
||||
# not a tr( call
|
||||
break
|
||||
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
|
||||
printerr("Begin quote not found in ", fpath, " line ", line_number)
|
||||
break
|
||||
var end_quote_index = find_unescaped_quote(line, begin_quote_index + 1)
|
||||
if end_quote_index == -1:
|
||||
# Multiline or procedural strings not supported
|
||||
printerr("End quote not found in ", fpath, " line ", 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
|
||||
printerr("End bracket not found in ", fpath, " line ", line_number)
|
||||
break
|
||||
_add_string(fpath, line_number, text)
|
||||
search_index = end_bracket_index
|
||||
|
||||
counter += 1
|
||||
assert(counter < 100)
|
||||
|
||||
|
||||
static func find_unescaped_quote(s, from):
|
||||
while true:
|
||||
var i = s.find('"', from)
|
||||
if i <= 0:
|
||||
return i
|
||||
if s[i - 1] != '\\':
|
||||
return i
|
||||
from = i + 1
|
||||
|
||||
|
||||
func _add_string(file, line_number, text):
|
||||
if not _strings.has(file):
|
||||
_strings[file] = {}
|
||||
_strings[file][text] = line_number
|
||||
|
||||
|
||||
static func _walk(folder_path, file_action, filter):
|
||||
#print("Walking dir ", folder_path)
|
||||
var d = Directory.new()
|
||||
var err = d.open(folder_path)
|
||||
if err != OK:
|
||||
printerr("Could not open directory '", folder_path, "', error ", err)
|
||||
return
|
||||
d.list_dir_begin(true, true)
|
||||
var fname = d.get_next()
|
||||
while fname != "":
|
||||
var fullpath = folder_path.plus_file(fname)
|
||||
if filter == null or filter.call_func(fullpath) == true:
|
||||
if d.current_is_dir():
|
||||
_walk(fullpath, file_action, filter)
|
||||
else:
|
||||
file_action.call_func(fullpath)
|
||||
fname = d.get_next()
|
||||
return
|
||||
|
127
addons/zylann.translation_editor/tools/extractor_dialog.gd
Normal file
127
addons/zylann.translation_editor/tools/extractor_dialog.gd
Normal file
@ -0,0 +1,127 @@
|
||||
tool
|
||||
extends WindowDialog
|
||||
|
||||
const Extractor = preload("extractor.gd")
|
||||
|
||||
signal import_selected(strings)
|
||||
|
||||
onready var _root_path_edit = get_node("VBoxContainer/HBoxContainer/RootPathEdit")
|
||||
onready var _summary_label = get_node("VBoxContainer/SummaryLabel")
|
||||
onready var _results_list = get_node("VBoxContainer/Results")
|
||||
onready var _progress_bar = get_node("VBoxContainer/ProgressBar")
|
||||
onready var _extract_button = get_node("VBoxContainer/Buttons/ExtractButton")
|
||||
onready var _import_button = get_node("VBoxContainer/Buttons/ImportButton")
|
||||
|
||||
var _extractor = null
|
||||
var _results = {}
|
||||
var _registered_string_filter = null
|
||||
|
||||
|
||||
func _ready():
|
||||
_import_button.disabled = true
|
||||
|
||||
|
||||
func set_registered_string_filter(registered_string_filter):
|
||||
assert(registered_string_filter is FuncRef)
|
||||
_registered_string_filter = registered_string_filter
|
||||
|
||||
|
||||
func _notification(what):
|
||||
if what == NOTIFICATION_VISIBILITY_CHANGED:
|
||||
if visible:
|
||||
_summary_label.text = ""
|
||||
_results.clear()
|
||||
_update_import_button()
|
||||
|
||||
|
||||
func _update_import_button():
|
||||
_import_button.disabled = (_results == null or len(_results) == 0)
|
||||
|
||||
|
||||
func _on_ExtractButton_pressed():
|
||||
if _extractor != null:
|
||||
return
|
||||
|
||||
var root = _root_path_edit.text.strip_edges()
|
||||
var d = Directory.new()
|
||||
if not d.dir_exists(root):
|
||||
printerr("Directory `", root, "` does not exist")
|
||||
return
|
||||
|
||||
_extractor = Extractor.new()
|
||||
_extractor.connect("finished", self, "_on_Extractor_finished")
|
||||
#_extractor.extract("res://", ["addons"])
|
||||
_extractor.extract("res://", [])
|
||||
|
||||
# TODO Progress reporting
|
||||
_progress_bar.value = 50
|
||||
|
||||
_extract_button.disabled = true
|
||||
_import_button.disabled = true
|
||||
|
||||
_summary_label.text = "Extracting..."
|
||||
|
||||
|
||||
func _on_ImportButton_pressed():
|
||||
emit_signal("import_selected", _results)
|
||||
_results.clear()
|
||||
hide()
|
||||
|
||||
|
||||
func _on_CancelButton_pressed():
|
||||
# TODO Cancel extraction?
|
||||
hide()
|
||||
|
||||
|
||||
func _on_Extractor_finished(results):
|
||||
print("Extractor finished")
|
||||
_progress_bar.value = 100
|
||||
|
||||
_results_list.clear()
|
||||
|
||||
var registered_set = {}
|
||||
var new_set = {}
|
||||
|
||||
# TODO We might actually want to not filter, in order to update location comments
|
||||
# Filter results
|
||||
if _registered_string_filter != null:
|
||||
|
||||
var fpaths = results.keys()
|
||||
for fpath in fpaths:
|
||||
var strings_dict = results[fpath]
|
||||
|
||||
var strings = strings_dict.keys()
|
||||
for text in strings:
|
||||
if _registered_string_filter.call_func(text):
|
||||
strings_dict.erase(text)
|
||||
registered_set[text] = true
|
||||
|
||||
if len(strings_dict) == 0:
|
||||
results.erase(fpath)
|
||||
|
||||
# Root
|
||||
_results_list.create_item()
|
||||
|
||||
for fpath in results:
|
||||
#print(fpath)
|
||||
var strings = results[fpath]
|
||||
|
||||
for text in strings:
|
||||
var line_number = strings[text]
|
||||
#print(" ", line_number, ": `", text, "`")
|
||||
|
||||
var item = _results_list.create_item()
|
||||
item.set_text(0, text)
|
||||
item.set_text(1, str(fpath, ": ", line_number))
|
||||
#item.set_tooltip(
|
||||
item.set_metadata(1, fpath)
|
||||
|
||||
new_set[text] = true
|
||||
|
||||
_results = results
|
||||
_extractor = null
|
||||
|
||||
_update_import_button()
|
||||
_extract_button.disabled = false
|
||||
|
||||
_summary_label.text = "{0} new, {1} registered".format([len(new_set), len(registered_set)])
|
311
addons/zylann.translation_editor/tools/extractor_dialog.tscn
Normal file
311
addons/zylann.translation_editor/tools/extractor_dialog.tscn
Normal file
@ -0,0 +1,311 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/zylann.translation_editor/tools/extractor_dialog.gd" type="Script" id=1]
|
||||
|
||||
[node name="ExtractorDialog" type="WindowDialog"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 54.0
|
||||
margin_top = 68.0
|
||||
margin_right = 694.0
|
||||
margin_bottom = 548.0
|
||||
rect_min_size = Vector2( 640, 480 )
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
popup_exclusive = false
|
||||
window_title = "String extractor"
|
||||
resizable = true
|
||||
script = ExtResource( 1 )
|
||||
_sections_unfolded = [ "Rect" ]
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="." index="1"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
margin_left = 8.0
|
||||
margin_top = 8.0
|
||||
margin_right = -8.0
|
||||
margin_bottom = -8.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 1
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
alignment = 0
|
||||
_sections_unfolded = [ "Margin" ]
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer" index="0"]
|
||||
|
||||
editor/display_folded = true
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_right = 624.0
|
||||
margin_bottom = 24.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 1
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
alignment = 0
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer" index="0"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 5.0
|
||||
margin_right = 29.0
|
||||
margin_bottom = 19.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 4
|
||||
text = "Root"
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
|
||||
[node name="RootPathEdit" type="LineEdit" parent="VBoxContainer/HBoxContainer" index="1"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 33.0
|
||||
margin_right = 624.0
|
||||
margin_bottom = 24.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 1
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
text = "res://"
|
||||
focus_mode = 2
|
||||
context_menu_enabled = true
|
||||
placeholder_alpha = 0.6
|
||||
caret_blink = false
|
||||
caret_blink_speed = 0.65
|
||||
caret_position = 0
|
||||
_sections_unfolded = [ "Size Flags" ]
|
||||
|
||||
[node name="SummaryLabel" type="Label" parent="VBoxContainer" index="1"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 28.0
|
||||
margin_right = 624.0
|
||||
margin_bottom = 42.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 4
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
|
||||
[node name="Results" type="Tree" parent="VBoxContainer" index="2"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 46.0
|
||||
margin_right = 624.0
|
||||
margin_bottom = 396.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = true
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 3
|
||||
columns = 2
|
||||
allow_reselect = false
|
||||
allow_rmb_select = false
|
||||
hide_folding = false
|
||||
hide_root = true
|
||||
drop_mode_flags = 0
|
||||
select_mode = 1
|
||||
_sections_unfolded = [ "Size Flags" ]
|
||||
|
||||
[node name="ProgressBar" type="ProgressBar" parent="VBoxContainer" index="3"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 400.0
|
||||
margin_right = 624.0
|
||||
margin_bottom = 416.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 0
|
||||
min_value = 0.0
|
||||
max_value = 100.0
|
||||
step = 1.0
|
||||
page = 0.0
|
||||
value = 0.0
|
||||
exp_edit = false
|
||||
rounded = false
|
||||
percent_visible = true
|
||||
|
||||
[node name="Spacer" type="Control" parent="VBoxContainer" index="4"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 420.0
|
||||
margin_right = 624.0
|
||||
margin_bottom = 428.0
|
||||
rect_min_size = Vector2( 0, 8 )
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
_sections_unfolded = [ "Rect" ]
|
||||
|
||||
[node name="Buttons" type="HBoxContainer" parent="VBoxContainer" index="5"]
|
||||
|
||||
editor/display_folded = true
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 432.0
|
||||
margin_right = 624.0
|
||||
margin_bottom = 452.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 1
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
custom_constants/separation = 16
|
||||
alignment = 1
|
||||
_sections_unfolded = [ "custom_constants" ]
|
||||
|
||||
[node name="ExtractButton" type="Button" parent="VBoxContainer/Buttons" index="0"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 184.0
|
||||
margin_right = 239.0
|
||||
margin_bottom = 20.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
toggle_mode = false
|
||||
enabled_focus_mode = 2
|
||||
shortcut = null
|
||||
group = null
|
||||
text = "Extract"
|
||||
flat = false
|
||||
align = 1
|
||||
|
||||
[node name="ImportButton" type="Button" parent="VBoxContainer/Buttons" index="1"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 255.0
|
||||
margin_right = 370.0
|
||||
margin_bottom = 20.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
toggle_mode = false
|
||||
enabled_focus_mode = 2
|
||||
shortcut = null
|
||||
group = null
|
||||
text = "Import selected"
|
||||
flat = false
|
||||
align = 1
|
||||
|
||||
[node name="CancelButton" type="Button" parent="VBoxContainer/Buttons" index="2"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_left = 386.0
|
||||
margin_right = 440.0
|
||||
margin_bottom = 20.0
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
focus_mode = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
toggle_mode = false
|
||||
enabled_focus_mode = 2
|
||||
shortcut = null
|
||||
group = null
|
||||
text = "Cancel"
|
||||
flat = false
|
||||
align = 1
|
||||
|
||||
[node name="Spacer2" type="Control" parent="VBoxContainer" index="6"]
|
||||
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
margin_top = 456.0
|
||||
margin_right = 624.0
|
||||
margin_bottom = 464.0
|
||||
rect_min_size = Vector2( 0, 8 )
|
||||
rect_pivot_offset = Vector2( 0, 0 )
|
||||
rect_clip_content = false
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
_sections_unfolded = [ "Rect" ]
|
||||
|
||||
[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"]
|
||||
|
||||
|
@ -19,6 +19,7 @@ func _enter_tree():
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
print("Translation editor plugin Exit tree")
|
||||
# The main control is not freed when the plugin is disabled
|
||||
_main_control.queue_free()
|
||||
_main_control = null
|
||||
|
@ -2,9 +2,8 @@
|
||||
|
||||
[ext_resource path="res://addons/zylann.translation_editor/tools/string_edition_dialog.gd" type="Script" id=1]
|
||||
|
||||
[node name="StringEditionDialog" type="WindowDialog"]
|
||||
[node name="StringEditionDialog" type="WindowDialog" index="0"]
|
||||
|
||||
visible = false
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
@ -20,7 +19,7 @@ mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
popup_exclusive = false
|
||||
window_title = "New string"
|
||||
window_title = "New string ID"
|
||||
resizable = false
|
||||
script = ExtResource( 1 )
|
||||
|
||||
@ -57,7 +56,6 @@ mouse_filter = 2
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 4
|
||||
text = "Already existing"
|
||||
percent_visible = 1.0
|
||||
lines_skipped = 0
|
||||
max_lines_visible = -1
|
||||
@ -139,6 +137,7 @@ mouse_filter = 0
|
||||
mouse_default_cursor_shape = 0
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
disabled = true
|
||||
toggle_mode = false
|
||||
enabled_focus_mode = 2
|
||||
shortcut = null
|
||||
|
@ -6,6 +6,7 @@ const PoLoader = preload("po_loader.gd")
|
||||
const Locales = preload("locales.gd")
|
||||
const StringEditionDialog = preload("string_edition_dialog.tscn")
|
||||
const LanguageSelectionDialog = preload("language_selection_dialog.tscn")
|
||||
const ExtractorDialog = preload("extractor_dialog.tscn")
|
||||
|
||||
const MENU_FILE_OPEN = 0
|
||||
const MENU_FILE_SAVE = 1
|
||||
@ -13,6 +14,7 @@ const MENU_FILE_SAVE_AS_CSV = 2
|
||||
const MENU_FILE_SAVE_AS_PO = 3
|
||||
const MENU_FILE_ADD_LANGUAGE = 4
|
||||
const MENU_FILE_REMOVE_LANGUAGE = 5
|
||||
const MENU_FILE_EXTRACT = 6
|
||||
|
||||
const FORMAT_CSV = 0
|
||||
const FORMAT_GETTEXT = 1
|
||||
@ -28,12 +30,14 @@ onready var _status_label = get_node("VBoxContainer/StatusBar/Label")
|
||||
var _string_edit_dialog = null
|
||||
var _language_selection_dialog = null
|
||||
var _remove_language_confirmation_dialog = null
|
||||
var _extractor_dialog = null
|
||||
var _open_dialog = null
|
||||
var _save_file_dialog = null
|
||||
var _save_folder_dialog = null
|
||||
# This is set when integrated as a Godot plugin
|
||||
var _base_control = null
|
||||
var _translation_edits = {}
|
||||
var _dialogs_to_free_on_exit = []
|
||||
|
||||
var _data = {}
|
||||
var _languages = []
|
||||
@ -54,6 +58,8 @@ func _ready():
|
||||
_file_menu.get_popup().add_separator()
|
||||
_file_menu.get_popup().add_item("Add language...", MENU_FILE_ADD_LANGUAGE)
|
||||
_file_menu.get_popup().add_item("Remove language", MENU_FILE_REMOVE_LANGUAGE)
|
||||
_file_menu.get_popup().add_separator()
|
||||
_file_menu.get_popup().add_item("Extractor", MENU_FILE_EXTRACT)
|
||||
_file_menu.get_popup().set_item_disabled(_file_menu.get_popup().get_item_index(MENU_FILE_REMOVE_LANGUAGE), true)
|
||||
_file_menu.get_popup().connect("id_pressed", self, "_on_FileMenu_id_pressed")
|
||||
|
||||
@ -70,40 +76,62 @@ func _ready():
|
||||
|
||||
|
||||
func _setup_dialogs(dialogs_parent):
|
||||
# If this fails, something wrong is happening with parenting of the main view
|
||||
assert(_open_dialog == null)
|
||||
|
||||
_open_dialog = FileDialog.new()
|
||||
_open_dialog.window_title = "Open translations"
|
||||
_open_dialog.add_filter("*.csv ; CSV files")
|
||||
_open_dialog.add_filter("*.po ; Gettext files")
|
||||
_open_dialog.mode = FileDialog.MODE_OPEN_FILE
|
||||
_open_dialog.connect("file_selected", self, "_on_OpenDialog_file_selected")
|
||||
dialogs_parent.add_child(_open_dialog)
|
||||
_add_dialog(dialogs_parent, _open_dialog)
|
||||
|
||||
_save_file_dialog = FileDialog.new()
|
||||
_save_file_dialog.window_title = "Save translations as CSV"
|
||||
_save_file_dialog.add_filter("*.csv ; CSV files")
|
||||
_save_file_dialog.mode = FileDialog.MODE_SAVE_FILE
|
||||
_save_file_dialog.connect("file_selected", self, "_on_SaveFileDialog_file_selected")
|
||||
dialogs_parent.add_child(_save_file_dialog)
|
||||
_add_dialog(dialogs_parent, _save_file_dialog)
|
||||
|
||||
_save_folder_dialog = FileDialog.new()
|
||||
_save_folder_dialog.window_title = "Save translations as gettext .po files"
|
||||
_save_folder_dialog.mode = FileDialog.MODE_OPEN_DIR
|
||||
_save_folder_dialog.connect("dir_selected", self, "_on_SaveFolderDialog_dir_selected")
|
||||
dialogs_parent.add_child(_save_folder_dialog)
|
||||
_add_dialog(dialogs_parent, _save_folder_dialog)
|
||||
|
||||
_string_edit_dialog = StringEditionDialog.instance()
|
||||
_string_edit_dialog.set_validator(funcref(self, "_validate_new_string_id"))
|
||||
_string_edit_dialog.connect("submitted", self, "_on_StringEditionDialog_submitted")
|
||||
dialogs_parent.add_child(_string_edit_dialog)
|
||||
_add_dialog(dialogs_parent, _string_edit_dialog)
|
||||
|
||||
_language_selection_dialog = LanguageSelectionDialog.instance()
|
||||
_language_selection_dialog.connect("language_selected", self, "_on_LanguageSelectionDialog_language_selected")
|
||||
dialogs_parent.add_child(_language_selection_dialog)
|
||||
_add_dialog(dialogs_parent, _language_selection_dialog)
|
||||
|
||||
_remove_language_confirmation_dialog = ConfirmationDialog.new()
|
||||
_remove_language_confirmation_dialog.dialog_text = "Do you really want to remove this language? (There is no undo!)"
|
||||
_remove_language_confirmation_dialog.connect("confirmed", self, "_on_RemoveLanguageConfirmationDialog_confirmed")
|
||||
dialogs_parent.add_child(_remove_language_confirmation_dialog)
|
||||
_add_dialog(dialogs_parent, _remove_language_confirmation_dialog)
|
||||
|
||||
_extractor_dialog = ExtractorDialog.instance()
|
||||
_extractor_dialog.set_registered_string_filter(funcref(self, "_is_string_registered"))
|
||||
_extractor_dialog.connect("import_selected", self, "_on_ExtractorDialog_import_selected")
|
||||
_add_dialog(dialogs_parent, _extractor_dialog)
|
||||
|
||||
|
||||
func _add_dialog(parent, dialog):
|
||||
parent.add_child(dialog)
|
||||
if parent != self:
|
||||
_dialogs_to_free_on_exit.append(dialog)
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
# Free dialogs because in the editor they might not be child of the main view...
|
||||
# Also this code runs in the edited scene view as a `tool` side-effect.
|
||||
for dialog in _dialogs_to_free_on_exit:
|
||||
dialog.queue_free()
|
||||
_dialogs_to_free_on_exit.clear()
|
||||
|
||||
|
||||
func configure_for_godot_integration(base_control):
|
||||
@ -137,6 +165,9 @@ func _on_FileMenu_id_pressed(id):
|
||||
var language = get_current_language()
|
||||
_remove_language_confirmation_dialog.window_title = str("Remove language `", language, "`")
|
||||
_remove_language_confirmation_dialog.popup_centered_minsize()
|
||||
|
||||
MENU_FILE_EXTRACT:
|
||||
_extractor_dialog.popup_centered_minsize()
|
||||
|
||||
|
||||
func _on_EditMenu_id_pressed(id):
|
||||
@ -400,6 +431,8 @@ func add_new_string(strid):
|
||||
}
|
||||
_data[strid] = s
|
||||
_string_list.add_item(strid)
|
||||
for language in _languages:
|
||||
_set_language_modified(language)
|
||||
|
||||
|
||||
func rename_string(old_strid, new_strid):
|
||||
@ -445,3 +478,22 @@ func _remove_language(language):
|
||||
func _on_RemoveLanguageConfirmationDialog_confirmed():
|
||||
var language = get_current_language()
|
||||
_remove_language(language)
|
||||
|
||||
|
||||
# Currently used as callback for filtering
|
||||
func _is_string_registered(text):
|
||||
if _data == null:
|
||||
print("No data")
|
||||
return false
|
||||
return _data.has(text)
|
||||
|
||||
|
||||
func _on_ExtractorDialog_import_selected(results):
|
||||
for fpath in results:
|
||||
var strings = results[fpath]
|
||||
for text in strings:
|
||||
# Checking because there might be duplicates,
|
||||
# strings can be found in multiple places
|
||||
if not _is_string_registered(text):
|
||||
add_new_string(text)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user