Use typed GDScript

This commit is contained in:
Marc Gilleron 2020-06-14 19:20:03 +01:00
parent 588011258e
commit bcad9cd60e
9 changed files with 280 additions and 275 deletions

View File

@ -1,25 +1,26 @@
tool
static func load_csv_translation(filepath):
var f = File.new()
var err = f.open(filepath, File.READ)
# TODO Can't type nullable return value
static func load_csv_translation(filepath: String):
var f := File.new()
var err := f.open(filepath, File.READ)
if err != OK:
printerr("Could not open ", filepath, " for read, code ", err)
return null
var first_row = f.get_csv_line()
var first_row := f.get_csv_line()
if first_row[0] != "id":
printerr("Translation file is missing the `id` column")
return null
var languages = PoolStringArray()
var languages := PoolStringArray()
for i in range(1, len(first_row)):
languages.append(first_row[i])
var ids = []
var rows = []
var ids := []
var rows := []
while not f.eof_reached():
var row = f.get_csv_line()
var row := f.get_csv_line()
if len(row) < 1 or row[0].strip_edges() == "":
printerr("Found an empty row")
continue
@ -33,9 +34,9 @@ static func load_csv_translation(filepath):
rows.append(trans)
f.close()
var translations = {}
var translations := {}
for i in len(ids):
var t = {}
var t := {}
for language_index in len(rows[i]):
t[languages[language_index]] = rows[i][language_index]
translations[ids[i]] = { "translations": t, "comments": "" }
@ -44,37 +45,37 @@ static func load_csv_translation(filepath):
class _Sorter:
func sort(a, b):
func sort(a: Array, b: Array):
return a[0] < b[0]
static func save_csv_translation(filepath, data):
static func save_csv_translation(filepath: String, data: Dictionary) -> Array:
print("Saving: ", data)
var languages = {}
var languages_set := {}
for id in data:
var s = data[id]
for language in s.translations:
languages[language] = true
languages_set[language] = true
if len(languages) == 0:
if len(languages_set) == 0:
printerr("No language found, nothing to save")
return []
languages = languages.keys()
var languages := languages_set.keys()
languages.sort()
var first_row = ["id"]
var first_row := ["id"]
first_row.resize(len(languages) + 1)
for i in len(languages):
first_row[i + 1] = languages[i]
var rows = []
var rows := []
rows.resize(len(data))
var row_index = 0
var row_index := 0
for id in data:
var s = data[id]
var row = []
var s : Dictionary = data[id]
var row := []
row.resize(len(languages) + 1)
row[0] = id
for i in len(languages):
@ -84,14 +85,14 @@ static func save_csv_translation(filepath, data):
row[i + 1] = text
rows[row_index] = row
row_index += 1
print(rows)
var sorter = _Sorter.new()
var sorter := _Sorter.new()
rows.sort_custom(sorter, "sort")
var delim = ","
var delim := ","
var f = File.new()
var err = f.open(filepath, File.WRITE)
var f := File.new()
var err := f.open(filepath, File.WRITE)
if err != OK:
printerr("Could not open ", filepath, " for write, code ", err)
return []
@ -102,15 +103,15 @@ static func save_csv_translation(filepath, data):
f.close()
print("Saved ", filepath)
var saved_languages = languages
var saved_languages := languages
return saved_languages
static func store_csv_line(f, a, delim = ","):
static func store_csv_line(f: File, a: Array, delim := ","):
for i in len(a):
if i > 0:
f.store_string(",")
var text = str(a[i])
var text := str(a[i])
# Behavior taken from LibreOffice
if text.find(delim) != -1 or text.find('"') != -1 or text.find("\n") != -1:
text = str('"', text.replace('"', '""'), '"')

View File

@ -8,26 +8,26 @@ signal progress_reported(ratio)
# TODO Do we want to know if a text is found multiple times in the same file?
# text => { file => line number }
var _strings = {}
var _thread = null
var _time_before = 0.0
var _ignored_paths = {}
var _paths = []
var _strings := {}
var _thread : Thread = null
var _time_before := 0.0
var _ignored_paths := {}
var _paths := []
func extract_async(root, ignored_paths=[]):
func extract_async(root: String, ignored_paths := []):
_prepare(root, ignored_paths)
_thread = Thread.new()
_thread.start(self, "_extract_thread_func", root)
func extract(root, ignored_paths=[]):
func extract(root: String, ignored_paths := []) -> Dictionary:
_prepare(root, ignored_paths)
_extract(root)
return _strings
func _prepare(root, ignored_paths):
func _prepare(root: String, ignored_paths: Array):
_time_before = OS.get_ticks_msec()
assert(_thread == null)
@ -38,17 +38,17 @@ func _prepare(root, ignored_paths):
_strings.clear()
func _extract(root):
func _extract(root: String):
_walk(root, funcref(self, "_index_file"), funcref(self, "_filter"))
for i in len(_paths):
var fpath = _paths[i]
var f = File.new()
var err = f.open(fpath, File.READ)
var fpath : String = _paths[i]
var f := File.new()
var err := f.open(fpath, File.READ)
if err != OK:
printerr("Could not open '", fpath, "', for read, error ", err)
continue
var ext = fpath.get_extension()
var ext := fpath.get_extension()
match ext:
"tscn":
_process_tscn(f, fpath)
@ -58,24 +58,24 @@ func _extract(root):
call_deferred("_report_progress", float(i) / float(len(_paths)))
func _extract_thread_func(root):
func _extract_thread_func(root: String):
_extract(root)
call_deferred("_finished")
func _report_progress(ratio):
func _report_progress(ratio: float):
emit_signal("progress_reported", ratio)
func _finished():
_thread.wait_to_finish()
_thread = null
var elapsed = float(OS.get_ticks_msec() - _time_before) / 1000.0
var elapsed := float(OS.get_ticks_msec() - _time_before) / 1000.0
print("Extraction took ", elapsed, " seconds")
emit_signal("finished", _strings)
func _filter(path):
func _filter(path: String) -> bool:
if path in _ignored_paths:
return false
if path[0] == ".":
@ -83,41 +83,40 @@ func _filter(path):
return true
func _index_file(fpath):
var ext = fpath.get_extension()
func _index_file(fpath: String):
var ext := fpath.get_extension()
#print("File ", fpath)
if ext != "tscn" and ext != "gd":
return
_paths.append(fpath)
func _process_tscn(f, fpath):
func _process_tscn(f: File, fpath: String):
# TOOD Also search for "window_title" and "dialog_text"
var pattern = "text ="
var text = ""
var state = STATE_SEARCHING
var line_number = 0
var pattern := "text ="
var text := ""
var state := STATE_SEARCHING
var line_number := 0
while not f.eof_reached():
var line = f.get_line()
var line := f.get_line()
line_number += 1
if line == "":
continue
match state:
STATE_SEARCHING:
var i = line.find(pattern)
var i := line.find(pattern)
if i != -1:
var begin_quote_index = line.find('"', i + len(pattern))
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('"')
var end_quote_index := line.rfind('"')
if end_quote_index != -1 and end_quote_index > begin_quote_index \
and line[end_quote_index - 1] != '\\':
@ -144,23 +143,23 @@ func _process_tscn(f, fpath):
text = str(text, line, "\n")
func _process_gd(f, fpath):
var text = ""
var line_number = 0
func _process_gd(f: File, fpath: String):
var text := ""
var line_number := 0
while not f.eof_reached():
var line = f.get_line().strip_edges()
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
var search_index := 0
var counter := 0
while true:
var pattern = "tr("
var call_index = line.find(pattern, search_index)
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)
@ -177,18 +176,18 @@ func _process_gd(f, fpath):
# 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)
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)
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)
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)
@ -201,7 +200,7 @@ func _process_gd(f, fpath):
assert(counter < 100)
static func find_unescaped_quote(s, from):
static func find_unescaped_quote(s, from) -> int:
while true:
var i = s.find('"', from)
if i <= 0:
@ -209,25 +208,26 @@ static func find_unescaped_quote(s, from):
if s[i - 1] != '\\':
return i
from = i + 1
return -1
func _add_string(file, line_number, text):
func _add_string(file: String, line_number: int, text: String):
if not _strings.has(text):
_strings[text] = {}
_strings[text][file] = line_number
static func _walk(folder_path, file_action, filter):
static func _walk(folder_path: String, file_action: FuncRef, filter: FuncRef):
#print("Walking dir ", folder_path)
var d = Directory.new()
var err = d.open(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()
var fname := d.get_next()
while fname != "":
var fullpath = folder_path.plus_file(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)

View File

@ -5,30 +5,29 @@ const Extractor = preload("./extractor.gd")
signal import_selected(strings)
onready var _root_path_edit = $VBoxContainer/HBoxContainer/RootPathEdit
onready var _excluded_dirs_edit = $VBoxContainer/Options/ExcludedDirsEdit
onready var _summary_label = $VBoxContainer/StatusBar/SummaryLabel
onready var _results_list = $VBoxContainer/Results
onready var _progress_bar = $VBoxContainer/StatusBar/ProgressBar
onready var _extract_button = $VBoxContainer/Buttons/ExtractButton
onready var _import_button = $VBoxContainer/Buttons/ImportButton
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
var _extractor = null
var _extractor : Extractor = null
# { string => { fpath => line number } }
var _results = {}
var _registered_string_filter = null
var _results := {}
var _registered_string_filter : FuncRef = null
func _ready():
_import_button.disabled = true
func set_registered_string_filter(registered_string_filter):
assert(registered_string_filter is FuncRef)
func set_registered_string_filter(registered_string_filter: FuncRef):
_registered_string_filter = registered_string_filter
func _notification(what):
func _notification(what: int):
if what == NOTIFICATION_VISIBILITY_CHANGED:
if visible:
_summary_label.text = ""
@ -44,13 +43,13 @@ func _on_ExtractButton_pressed():
if _extractor != null:
return
var root = _root_path_edit.text.strip_edges()
var d = Directory.new()
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
var excluded_dirs = _excluded_dirs_edit.text.split(";", false)
var excluded_dirs := _excluded_dirs_edit.text.split(";", false)
for i in len(excluded_dirs):
excluded_dirs[i] = excluded_dirs[i].strip_edges()
@ -82,20 +81,20 @@ func _on_Extractor_progress_reported(ratio):
_progress_bar.value = 100.0 * ratio
func _on_Extractor_finished(results):
func _on_Extractor_finished(results: Dictionary):
print("Extractor finished")
_progress_bar.value = 100
_progress_bar.hide()
_results_list.clear()
var registered_set = {}
var new_set = {}
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 texts = results.keys()
var texts := results.keys()
for text in texts:
if _registered_string_filter.call_func(text):
results.erase(text)
@ -105,16 +104,16 @@ func _on_Extractor_finished(results):
_results_list.create_item()
for text in results:
var item = _results_list.create_item()
var item : TreeItem = _results_list.create_item()
item.set_text(0, text)
item.collapsed = true
new_set[text] = true
var files = results[text]
for file in files:
var line_number = files[file]
var line_number : int = files[file]
var file_item = _results_list.create_item(item)
var file_item : TreeItem = _results_list.create_item(item)
file_item.set_text(0, str(file, ": ", line_number))
_results = results

View File

@ -5,11 +5,11 @@ const Locales = preload("./locales.gd")
signal language_selected(language)
onready var _filter_edit = $VBoxContainer/FilterEdit
onready var _languages_list = $VBoxContainer/LanguagesList
onready var _ok_button = $VBoxContainer/Buttons/OkButton
onready var _filter_edit : LineEdit = $VBoxContainer/FilterEdit
onready var _languages_list : Tree = $VBoxContainer/LanguagesList
onready var _ok_button : Button = $VBoxContainer/Buttons/OkButton
var _hidden_locales = []
var _hidden_locales := []
func configure(hidden_locales):
@ -20,8 +20,8 @@ func configure(hidden_locales):
func refresh_list():
_languages_list.clear()
var filter = _filter_edit.text.strip_edges()
var locales = Locales.get_all_locales()
var filter := _filter_edit.text.strip_edges()
var locales := Locales.get_all_locales()
# Hidden root
_languages_list.create_item()
@ -31,7 +31,7 @@ func refresh_list():
continue
if filter != "" and locale[0].findn(filter) == -1:
continue
var item = _languages_list.create_item()
var item : TreeItem = _languages_list.create_item()
item.set_text(0, locale[0])
item.set_text(1, locale[1])
@ -39,7 +39,7 @@ func refresh_list():
func submit():
var item = _languages_list.get_selected()
var item := _languages_list.get_selected()
emit_signal("language_selected", item.get_text(0))
hide()

View File

@ -363,12 +363,12 @@ const _locales = [
]
static func get_all_locales():
static func get_all_locales() -> Array:
return _locales
static func get_all_locale_ids():
var ids = []
static func get_all_locale_ids() -> Array:
var ids := []
ids.resize(len(_locales))
for i in len(_locales):
ids[i] = _locales[i][0]

View File

@ -1,18 +1,19 @@
tool
extends EditorPlugin
var TranslationEditor = load("res://addons/zylann.translation_editor/tools/translation_editor.tscn")
const TranslationEditor = preload("./translation_editor.gd")
const TranslationEditorScene = preload("./translation_editor.tscn")
var _main_control = null
var _main_control : TranslationEditor = null
func _enter_tree():
print("Translation editor plugin Enter tree")
var editor_interface = get_editor_interface()
var base_control = editor_interface.get_base_control()
var editor_interface := get_editor_interface()
var base_control := editor_interface.get_base_control()
_main_control = TranslationEditor.instance()
_main_control = TranslationEditorScene.instance()
_main_control.configure_for_godot_integration(base_control)
_main_control.hide()
editor_interface.get_editor_viewport().add_child(_main_control)
@ -25,19 +26,19 @@ func _exit_tree():
_main_control = null
func has_main_screen():
func has_main_screen() -> bool:
return true
func get_plugin_name():
func get_plugin_name() -> String:
return "Localization"
func get_plugin_icon():
func get_plugin_icon() -> Texture:
return preload("icons/icon_translation_editor.svg")
func make_visible(visible):
func make_visible(visible: bool):
_main_control.visible = visible

View File

@ -4,51 +4,52 @@ const STATE_NONE = 0
const STATE_MSGID = 1
const STATE_MSGSTR = 2
static func load_po_translation(folder_path, valid_locales):
var all_strings = {}
var config = {}
# TODO Can't type nullable result
static func load_po_translation(folder_path: String, valid_locales: Array):
var all_strings := {}
var config := {}
# TODO Get languages from configs, not from filenames
var languages = get_languages_in_folder(folder_path, valid_locales)
var languages := get_languages_in_folder(folder_path, valid_locales)
if len(languages) == 0:
printerr("No .po languages were found in ", folder_path)
return all_strings
for language in languages:
var filepath = folder_path.plus_file(str(language, ".po"))
var filepath := folder_path.plus_file(str(language, ".po"))
var f = File.new()
var err = f.open(filepath, File.READ)
var f := File.new()
var err := f.open(filepath, File.READ)
if err != OK:
printerr("Could not open file ", filepath, " for read, error ", err)
return null
f.store_line("")
var state = STATE_NONE
var comment = ""
var msgid = ""
var msgstr = ""
var ids = []
var translations = []
var comments = []
var state := STATE_NONE
var comment := ""
var msgid := ""
var msgstr := ""
var ids := []
var translations := []
var comments := []
# For debugging
var line_number = -1
var line_number := -1
while not f.eof_reached():
var line = f.get_line().strip_edges()
var line := f.get_line().strip_edges()
line_number += 1
if line != "" and line[0] == "#":
var comment_line = line.right(1).strip_edges()
var comment_line := line.right(1).strip_edges()
if comment == "":
comment = str(comment, comment_line)
else:
comment = str(comment, "\n", comment_line)
continue
var space_index = line.find(" ")
var space_index := line.find(" ")
if line.begins_with("msgid"):
msgid = _parse_msg(line.right(space_index))
@ -66,7 +67,7 @@ static func load_po_translation(folder_path, valid_locales):
msgstr = str(msgstr, _parse_msg(line))
elif line == "" and state == STATE_MSGSTR:
var s = null
var s : Dictionary
if msgid == "":
assert(len(msgstr) != 0)
config = _parse_config(msgstr)
@ -96,7 +97,7 @@ static func load_po_translation(folder_path, valid_locales):
return all_strings
static func _parse_msg(s):
static func _parse_msg(s: String) -> String:
s = s.strip_edges()
assert(s[0] == '"')
var end = s.rfind('"')
@ -104,9 +105,9 @@ static func _parse_msg(s):
return msg.c_unescape().replace('\\"', '"')
static func _parse_config(text):
var config = {}
var lines = text.split("\n", false)
static func _parse_config(text: String) -> Dictionary:
var config := {}
var lines := text.split("\n", false)
print("Config lines: ", lines)
for line in lines:
var splits = line.split(":")
@ -116,25 +117,26 @@ static func _parse_config(text):
class _Sorter:
func sort(a, b):
func sort(a: Array, b: Array):
return a[0] < b[0]
static func save_po_translations(folder_path, translations, languages_to_save):
var sorter = _Sorter.new()
var saved_languages = []
static func save_po_translations(folder_path: String, translations: Dictionary,
languages_to_save: Array) -> Array:
var sorter := _Sorter.new()
var saved_languages := []
for language in languages_to_save:
var f = File.new()
var filepath = folder_path.plus_file(str(language, ".po"))
var err = f.open(filepath, File.WRITE)
var f := File.new()
var filepath := folder_path.plus_file(str(language, ".po"))
var err := f.open(filepath, File.WRITE)
if err != OK:
printerr("Could not open file ", filepath, " for write, error ", err)
continue
# TODO Take as argument
var config = {
var config := {
"Project-Id-Version": ProjectSettings.get_setting("application/config/name"),
"MIME-Version": "1.0",
"Content-Type": "text/plain; charset=UTF-8",
@ -143,17 +145,17 @@ static func save_po_translations(folder_path, translations, languages_to_save):
}
# Write config
var config_msg = ""
var config_msg := ""
for k in config:
config_msg = str(config_msg, k, ": ", config[k], "\n")
_write_msg(f, "msgid", "")
_write_msg(f, "msgstr", config_msg)
f.store_line("")
var items = []
var items := []
for id in translations:
var s = translations[id]
var s : Dictionary = translations[id]
if not s.translations.has(language):
continue
items.append([id, s.translations[language], s.comments])
@ -161,8 +163,7 @@ static func save_po_translations(folder_path, translations, languages_to_save):
items.sort_custom(sorter, "sort")
for item in items:
var comment = item[2]
var comment : String = item[2]
if comment != "":
var comment_lines = comment.split("\n")
for line in comment_lines:
@ -179,8 +180,8 @@ static func save_po_translations(folder_path, translations, languages_to_save):
return saved_languages
static func _write_msg(f, msgtype, msg):
var lines = msg.split("\n")
static func _write_msg(f: File, msgtype: String, msg: String):
var lines := msg.split("\n")
# `split` removes the newlines, so we'll add them back.
# Empty lines may happen if the original text has multiple successsive line breaks.
# However, if the text ends with a newline, it will produce an empty string at the end,
@ -216,20 +217,20 @@ static func _write_msg(f, msgtype, msg):
f.store_line(str(" \"", lines[i], "\""))
static func get_languages_in_folder(folder_path, valid_locales):
var result = []
var d = Directory.new()
var err = d.open(folder_path)
static func get_languages_in_folder(folder_path: String, valid_locales: Array) -> Array:
var result := []
var d := Directory.new()
var err := d.open(folder_path)
if err != OK:
printerr("Could not open directory ", folder_path, ", error ", err)
return result
d.list_dir_begin()
var fname = d.get_next()
var fname := d.get_next()
while fname != "":
if not d.current_is_dir():
var ext = fname.get_extension()
var ext := fname.get_extension()
if ext == "po":
var language = fname.get_basename().get_file()
var language := fname.get_basename().get_file()
if valid_locales.find(language) != -1:
result.append(language)
fname = d.get_next()

View File

@ -3,29 +3,27 @@ extends WindowDialog
signal submitted(str_id, prev_str_id)
onready var _line_edit = $VBoxContainer/LineEdit
onready var _ok_button = $VBoxContainer/Buttons/OkButton
onready var _hint_label = $VBoxContainer/HintLabel
onready var _line_edit : LineEdit = $VBoxContainer/LineEdit
onready var _ok_button : Button = $VBoxContainer/Buttons/OkButton
onready var _hint_label : Label = $VBoxContainer/HintLabel
var _validator_func = null
var _prev_str_id = null
var _validator_func : FuncRef = null
var _prev_str_id := ""
func set_replaced_str_id(str_id):
assert(typeof(str_id) == TYPE_STRING or str_id == null)
func set_replaced_str_id(str_id: String):
_prev_str_id = str_id
if typeof(str_id) == TYPE_STRING:
_line_edit.text = str_id
_line_edit.text = str_id
func set_validator(f):
func set_validator(f: FuncRef):
_validator_func = f
func _notification(what):
func _notification(what: int):
if what == NOTIFICATION_VISIBILITY_CHANGED:
if visible:
if _prev_str_id == null:
if _prev_str_id == "":
window_title = "New string ID"
else:
window_title = str("Replace `", _prev_str_id, "`")
@ -33,14 +31,14 @@ func _notification(what):
_validate()
func _on_LineEdit_text_changed(new_text):
func _on_LineEdit_text_changed(new_text: String):
_validate()
func _validate():
var new_text = _line_edit.text.strip_edges()
var valid = not new_text.empty()
var hint_message = ""
var new_text := _line_edit.text.strip_edges()
var valid := not new_text.empty()
var hint_message := ""
if _validator_func != null:
var res = _validator_func.call_func(new_text)
@ -54,7 +52,7 @@ func _validate():
# Note: hiding the label would shift up other controls in the container
func _on_LineEdit_text_entered(new_text):
func _on_LineEdit_text_entered(new_text: String):
submit()
@ -67,7 +65,7 @@ func _on_CancelButton_pressed():
func submit():
var s = _line_edit.text.strip_edges()
var s := _line_edit.text.strip_edges()
emit_signal("submitted", s, _prev_str_id)
hide()

View File

@ -4,9 +4,13 @@ extends Panel
const CsvLoader = preload("./csv_loader.gd")
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 StringEditionDialog = preload("./string_edition_dialog.gd")
const LanguageSelectionDialog = preload("./language_selection_dialog.gd")
const ExtractorDialog = preload("./extractor_dialog.gd")
const StringEditionDialogScene = preload("./string_edition_dialog.tscn")
const LanguageSelectionDialogScene = preload("./language_selection_dialog.tscn")
const ExtractorDialogScene = preload("./extractor_dialog.tscn")
const MENU_FILE_OPEN = 0
const MENU_FILE_SAVE = 1
@ -19,34 +23,35 @@ const MENU_FILE_EXTRACT = 6
const FORMAT_CSV = 0
const FORMAT_GETTEXT = 1
onready var _file_menu = $VBoxContainer/MenuBar/FileMenu
onready var _edit_menu = $VBoxContainer/MenuBar/EditMenu
onready var _search_edit = $VBoxContainer/Main/LeftPane/Search/Search
onready var _clear_search_button = $VBoxContainer/Main/LeftPane/Search/ClearSearch
onready var _string_list = $VBoxContainer/Main/LeftPane/StringList
onready var _translation_tab_container = \
onready var _file_menu : MenuButton = $VBoxContainer/MenuBar/FileMenu
onready var _edit_menu : MenuButton = $VBoxContainer/MenuBar/EditMenu
onready var _search_edit : LineEdit = $VBoxContainer/Main/LeftPane/Search/Search
onready var _clear_search_button : Button = $VBoxContainer/Main/LeftPane/Search/ClearSearch
onready var _string_list : ItemList = $VBoxContainer/Main/LeftPane/StringList
onready var _translation_tab_container : TabContainer = \
$VBoxContainer/Main/RightPane/VSplitContainer/TranslationTabContainer
onready var _notes_edit = $VBoxContainer/Main/RightPane/VSplitContainer/VBoxContainer/NotesEdit
onready var _status_label = $VBoxContainer/StatusBar/Label
onready var _notes_edit : TextEdit = \
$VBoxContainer/Main/RightPane/VSplitContainer/VBoxContainer/NotesEdit
onready var _status_label : Label = $VBoxContainer/StatusBar/Label
var _string_edit_dialog = null
var _language_selection_dialog = null
var _remove_language_confirmation_dialog = null
var _remove_string_confirmation_dialog = null
var _extractor_dialog = null
var _open_dialog = null
var _save_file_dialog = null
var _save_folder_dialog = null
var _string_edit_dialog : StringEditionDialog = null
var _language_selection_dialog : LanguageSelectionDialog = null
var _remove_language_confirmation_dialog : ConfirmationDialog = null
var _remove_string_confirmation_dialog : ConfirmationDialog = null
var _extractor_dialog : ExtractorDialog = null
var _open_dialog : FileDialog = null
var _save_file_dialog : FileDialog = null
var _save_folder_dialog : FileDialog = null
# This is set when integrated as a Godot plugin
var _base_control = null
var _translation_edits = {}
var _dialogs_to_free_on_exit = []
var _base_control : Control = null
var _translation_edits := {}
var _dialogs_to_free_on_exit := []
var _data = {}
var _languages = []
var _current_path = null
var _current_format = FORMAT_CSV
var _modified_languages = {}
var _data := {}
var _languages := []
var _current_path := ""
var _current_format := FORMAT_CSV
var _modified_languages := {}
func _ready():
@ -100,12 +105,12 @@ func _setup_dialogs():
_save_folder_dialog.connect("dir_selected", self, "_on_SaveFolderDialog_dir_selected")
_add_dialog(_save_folder_dialog)
_string_edit_dialog = StringEditionDialog.instance()
_string_edit_dialog = StringEditionDialogScene.instance()
_string_edit_dialog.set_validator(funcref(self, "_validate_new_string_id"))
_string_edit_dialog.connect("submitted", self, "_on_StringEditionDialog_submitted")
_add_dialog(_string_edit_dialog)
_language_selection_dialog = LanguageSelectionDialog.instance()
_language_selection_dialog = LanguageSelectionDialogScene.instance()
_language_selection_dialog.connect(
"language_selected", self, "_on_LanguageSelectionDialog_language_selected")
_add_dialog(_language_selection_dialog)
@ -117,7 +122,7 @@ func _setup_dialogs():
"confirmed", self, "_on_RemoveLanguageConfirmationDialog_confirmed")
_add_dialog(_remove_language_confirmation_dialog)
_extractor_dialog = ExtractorDialog.instance()
_extractor_dialog = ExtractorDialogScene.instance()
_extractor_dialog.set_registered_string_filter(funcref(self, "_is_string_registered"))
_extractor_dialog.connect("import_selected", self, "_on_ExtractorDialog_import_selected")
_add_dialog(_extractor_dialog)
@ -130,7 +135,7 @@ func _setup_dialogs():
_add_dialog(_remove_string_confirmation_dialog)
func _add_dialog(dialog):
func _add_dialog(dialog: Control):
if _base_control != null:
_base_control.add_child(dialog)
_dialogs_to_free_on_exit.append(dialog)
@ -146,7 +151,7 @@ func _exit_tree():
_dialogs_to_free_on_exit.clear()
func configure_for_godot_integration(base_control):
func configure_for_godot_integration(base_control: Control):
# You have to call this before adding to the tree
assert(not is_inside_tree())
_base_control = base_control
@ -155,7 +160,7 @@ func configure_for_godot_integration(base_control):
self_modulate = Color(0, 0, 0, 0)
func _on_FileMenu_id_pressed(id):
func _on_FileMenu_id_pressed(id: int):
match id:
MENU_FILE_OPEN:
_open()
@ -174,7 +179,7 @@ func _on_FileMenu_id_pressed(id):
_language_selection_dialog.popup_centered_ratio()
MENU_FILE_REMOVE_LANGUAGE:
var language = get_current_language()
var language := get_current_language()
_remove_language_confirmation_dialog.window_title = \
str("Remove language `", language, "`")
_remove_language_confirmation_dialog.popup_centered_minsize()
@ -183,19 +188,19 @@ func _on_FileMenu_id_pressed(id):
_extractor_dialog.popup_centered_minsize()
func _on_EditMenu_id_pressed(id):
func _on_EditMenu_id_pressed(id: int):
pass
func _on_OpenDialog_file_selected(filepath):
func _on_OpenDialog_file_selected(filepath: String):
load_file(filepath)
func _on_SaveFileDialog_file_selected(filepath):
func _on_SaveFileDialog_file_selected(filepath: String):
save_file(filepath, FORMAT_CSV)
func _on_SaveFolderDialog_dir_selected(filepath):
func _on_SaveFolderDialog_dir_selected(filepath: String):
save_file(filepath, FORMAT_GETTEXT)
@ -207,7 +212,7 @@ func _on_SaveButton_pressed():
_save()
func _on_LanguageSelectionDialog_language_selected(language):
func _on_LanguageSelectionDialog_language_selected(language: String):
_add_language(language)
@ -216,18 +221,18 @@ func _open():
func _save():
if _current_path == null:
if _current_path == "":
# Have to default to CSV for now...
_save_file_dialog.popup_centered_ratio()
else:
save_file(_current_path, _current_format)
func load_file(filepath):
var ext = filepath.get_extension()
func load_file(filepath: String):
var ext := filepath.get_extension()
if ext == "po":
var valid_locales = Locales.get_all_locale_ids()
var valid_locales := Locales.get_all_locale_ids()
_current_path = filepath.get_base_dir()
_data = PoLoader.load_po_translation(_current_path, valid_locales)
_current_format = FORMAT_GETTEXT
@ -243,7 +248,7 @@ func load_file(filepath):
_languages.clear()
for strid in _data:
var s = _data[strid]
var s : Dictionary = _data[strid]
for language in s.translations:
if _languages.find(language) == -1:
_languages.append(language)
@ -264,7 +269,7 @@ func load_file(filepath):
func _update_status_label():
if _current_path == null:
if _current_path == "":
_status_label.text = "No file loaded"
elif _current_format == FORMAT_CSV:
_status_label.text = _current_path
@ -272,27 +277,27 @@ func _update_status_label():
_status_label.text = str(_current_path, " (Gettext translations folder)")
func _create_translation_edit(language):
func _create_translation_edit(language: String):
assert(not _translation_edits.has(language)) # boom
var edit = TextEdit.new()
var edit := TextEdit.new()
edit.hide()
var tab_index = _translation_tab_container.get_tab_count()
var tab_index := _translation_tab_container.get_tab_count()
_translation_tab_container.add_child(edit)
_translation_tab_container.set_tab_title(tab_index, language)
_translation_edits[language] = edit
edit.connect("text_changed", self, "_on_TranslationEdit_text_changed", [language])
func _on_TranslationEdit_text_changed(language):
var edit = _translation_edits[language]
var selected_strids = _string_list.get_selected_items()
func _on_TranslationEdit_text_changed(language: String):
var edit : TextEdit = _translation_edits[language]
var selected_strids := _string_list.get_selected_items()
# TODO Don't show the editor if no strings are selected
if len(selected_strids) != 1:
return
#assert(len(selected_strids) == 1)
var strid = _string_list.get_item_text(selected_strids[0])
var prev_text = null
var s = _data[strid]
var strid := _string_list.get_item_text(selected_strids[0])
var prev_text : String
var s : Dictionary = _data[strid]
if s.translations.has(language):
prev_text = s.translations[language]
if prev_text != edit.text:
@ -301,34 +306,34 @@ func _on_TranslationEdit_text_changed(language):
func _on_NotesEdit_text_changed():
var selected_strids = _string_list.get_selected_items()
var selected_strids := _string_list.get_selected_items()
# TODO Don't show the editor if no strings are selected
if len(selected_strids) != 1:
return
#assert(len(selected_strids) == 1)
var strid = _string_list.get_item_text(selected_strids[0])
var s = _data[strid]
var strid := _string_list.get_item_text(selected_strids[0])
var s : Dictionary = _data[strid]
if s.comments != _notes_edit.text:
s.comments = _notes_edit.text
for language in _languages:
_set_language_modified(language)
func _set_language_modified(language):
func _set_language_modified(language: String):
if _modified_languages.has(language):
return
_modified_languages[language] = true
_set_language_tab_title(language, str(language, "*"))
func _set_language_unmodified(language):
func _set_language_unmodified(language: String):
if not _modified_languages.has(language):
return
_modified_languages.erase(language)
_set_language_tab_title(language, language)
func _set_language_tab_title(language, title):
func _set_language_tab_title(language: String, title: String):
var page = _translation_edits[language]
for i in _translation_tab_container.get_child_count():
if _translation_tab_container.get_child(i) == page:
@ -341,21 +346,21 @@ func _set_language_tab_title(language, title):
assert(false)
func get_current_language():
func get_current_language() -> String:
var page = _translation_tab_container.get_current_tab_control()
for language in _translation_edits:
if _translation_edits[language] == page:
return language
# Something bad happened
assert(false)
return null
return ""
func save_file(path, format):
var saved_languages = []
func save_file(path: String, format: int):
var saved_languages := []
if format == FORMAT_GETTEXT:
var languages_to_save
var languages_to_save : Array
if _current_format != FORMAT_GETTEXT:
languages_to_save = _languages
else:
@ -377,9 +382,9 @@ func save_file(path, format):
func refresh_list():
var search_text = _search_edit.text.strip_edges()
var search_text := _search_edit.text.strip_edges()
var sorted_strids = []
var sorted_strids := []
if search_text == "":
sorted_strids = _data.keys()
else:
@ -394,11 +399,11 @@ func refresh_list():
_string_list.add_item(strid)
func _on_StringList_item_selected(index):
var str_id = _string_list.get_item_text(index)
var s = _data[str_id]
func _on_StringList_item_selected(index: int):
var str_id := _string_list.get_item_text(index)
var s : Dictionary = _data[str_id]
for language in _languages:
var e = _translation_edits[language]
var e : TextEdit = _translation_edits[language]
#e.show()
if s.translations.has(language):
e.text = s.translations[language]
@ -408,7 +413,7 @@ func _on_StringList_item_selected(index):
func _on_AddButton_pressed():
_string_edit_dialog.set_replaced_str_id(null)
_string_edit_dialog.set_replaced_str_id("")
_string_edit_dialog.popup_centered()
@ -416,17 +421,17 @@ func _on_RemoveButton_pressed():
var selected_items = _string_list.get_selected_items()
if len(selected_items) == 0:
return
var str_id = _string_list.get_item_text(selected_items[0])
var str_id := _string_list.get_item_text(selected_items[0])
_remove_string_confirmation_dialog.window_title = str("Remove `", str_id, "`")
_remove_string_confirmation_dialog.popup_centered_minsize()
func _on_RemoveStringConfirmationDialog_confirmed():
var selected_items = _string_list.get_selected_items()
var selected_items := _string_list.get_selected_items()
if len(selected_items) == 0:
printerr("No selected string??")
return
var strid = _string_list.get_item_text(selected_items[0])
var strid := _string_list.get_item_text(selected_items[0])
_string_list.remove_item(selected_items[0])
_data.erase(strid)
for language in _languages:
@ -434,22 +439,22 @@ func _on_RemoveStringConfirmationDialog_confirmed():
func _on_RenameButton_pressed():
var selected_items = _string_list.get_selected_items()
var selected_items := _string_list.get_selected_items()
if len(selected_items) == 0:
return
var str_id = _string_list.get_item_text(selected_items[0])
var str_id := _string_list.get_item_text(selected_items[0])
_string_edit_dialog.set_replaced_str_id(str_id)
_string_edit_dialog.popup_centered()
func _on_StringEditionDialog_submitted(str_id, prev_str_id):
if prev_str_id == null:
func _on_StringEditionDialog_submitted(str_id: String, prev_str_id: String):
if prev_str_id == "":
add_new_string(str_id)
else:
rename_string(prev_str_id, str_id)
func _validate_new_string_id(str_id):
func _validate_new_string_id(str_id: String):
if _data.has(str_id):
return "Already existing"
if str_id.strip_edges() != str_id:
@ -460,10 +465,10 @@ func _validate_new_string_id(str_id):
return true
func add_new_string(strid):
func add_new_string(strid: String):
print("Adding new string ", strid)
assert(not _data.has(strid))
var s = {
var s := {
"translations": {},
"comments": ""
}
@ -473,9 +478,9 @@ func add_new_string(strid):
_set_language_modified(language)
func rename_string(old_strid, new_strid):
func rename_string(old_strid: String, new_strid: String):
assert(_data.has(old_strid))
var s = _data[old_strid]
var s : Dictionary = _data[old_strid]
_data.erase(old_strid)
_data[new_strid] = s
for i in _string_list.get_item_count():
@ -484,24 +489,24 @@ func rename_string(old_strid, new_strid):
break
func _add_language(language):
func _add_language(language: String):
assert(_languages.find(language) == -1)
_create_translation_edit(language)
_languages.append(language)
_set_language_modified(language)
var menu_index = _file_menu.get_popup().get_item_index(MENU_FILE_REMOVE_LANGUAGE)
var menu_index := _file_menu.get_popup().get_item_index(MENU_FILE_REMOVE_LANGUAGE)
_file_menu.get_popup().set_item_disabled(menu_index, false)
print("Added language ", language)
func _remove_language(language):
func _remove_language(language: String):
assert(_languages.find(language) != -1)
_set_language_unmodified(language)
var edit = _translation_edits[language]
var edit : TextEdit = _translation_edits[language]
edit.queue_free()
_translation_edits.erase(language)
_languages.erase(language)
@ -514,25 +519,25 @@ func _remove_language(language):
func _on_RemoveLanguageConfirmationDialog_confirmed():
var language = get_current_language()
var language := get_current_language()
_remove_language(language)
# Used as callback for filtering
func _is_string_registered(text):
func _is_string_registered(text: String) -> bool:
if _data == null:
print("No data")
return false
return _data.has(text)
func _on_ExtractorDialog_import_selected(results):
func _on_ExtractorDialog_import_selected(results: Dictionary):
for text in results:
if not _is_string_registered(text):
add_new_string(text)
func _on_Search_text_changed(search_text):
func _on_Search_text_changed(search_text: String):
_clear_search_button.visible = (search_text != "")
refresh_list()