commit d41fb3e7862267105810f17ca777e8fd614dc76d Author: Marc Gilleron Date: Tue Nov 13 21:37:26 2018 +0000 Added project diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a96203 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.import/ diff --git a/addons/zylann.translation_editor/plugin.cfg b/addons/zylann.translation_editor/plugin.cfg new file mode 100644 index 0000000..8fac07e --- /dev/null +++ b/addons/zylann.translation_editor/plugin.cfg @@ -0,0 +1,8 @@ +[plugin] + +name="Translation Editor" +description="Integrated translations editor" +author="Marc Gilleron" +version="0.1" +script="tools/plugin.gd" + diff --git a/addons/zylann.translation_editor/tools/csv_loader.gd b/addons/zylann.translation_editor/tools/csv_loader.gd new file mode 100644 index 0000000..815af2f --- /dev/null +++ b/addons/zylann.translation_editor/tools/csv_loader.gd @@ -0,0 +1,119 @@ +tool + +static func load_csv_translation(filepath): + 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() + if first_row[0] != "id": + printerr("Translation file is missing the `id` column") + return null + + var languages = PoolStringArray() + for i in range(1, len(first_row)): + languages.append(first_row[i]) + + var ids = [] + var rows = [] + while not f.eof_reached(): + var row = f.get_csv_line() + if len(row) < 1 or row[0].strip_edges() == "": + printerr("Found an empty row") + continue + if len(row) < len(first_row): + print("Found row smaller than header, resizing") + row.resize(len(first_row)) + ids.append(row[0]) + var trans = PoolStringArray() + for i in range(1, len(row)): + trans.append(row[i]) + rows.append(trans) + f.close() + + var translations = {} + for i in len(ids): + var t = {} + for language_index in len(rows[i]): + t[languages[language_index]] = rows[i][language_index] + translations[ids[i]] = { "translations": t, "comments": "" } + + return translations + + +class _Sorter: + func sort(a, b): + return a[0] < b[0] + + +static func save_csv_translation(filepath, data): + var languages = {} + for id in data: + var s = data[id] + for language in s.translations: + languages[id] = true + + assert(len(data.languages) > 0) + + languages = languages.keys() + languages.sort() + + var first_row = [] + first_row.resize(len(languages) + 1) + for i in len(languages): + first_row[i + 1] = languages[i] + + var rows = [] + rows.resize(len(data)) + + var row_index = 0 + for id in data: + var s = data[id] + var row = [] + row.resize(len(languages) + 1) + row[0] = id + for i in len(languages): + var text = "" + if s.translations.has(languages[i]): + text = s.translations[languages[i]] + row[i] = text + rows[row_index] = row + row_index += 1 + + var sorter = _Sorter.new() + rows.sort_custom(sorter, "sort") + + var delim = "," + + var f = File.new() + var err = f.open(filepath, File.WRITE) + if err != OK: + printerr("Could not open ", filepath, " for write, code ", err) + return false + + for h in first_row: + f.store_string(str(",", h)) + f.store_string("\n") + + for row in rows: + for i in len(row): + var text = row[i] + if i == 0: + f.store_string(text) + else: + f.store_string(",") + # Behavior taken from LibreOffice + if text.contains(delim): + if text.contains('"'): + text = text.replace('"', '""') + text = str('"', text, '"') + f.store_string(text) + f.store_string("\n") + + f.close() + print("Saved ", filepath) + return true + + diff --git a/addons/zylann.translation_editor/tools/icons/icon_load.svg b/addons/zylann.translation_editor/tools/icons/icon_load.svg new file mode 100644 index 0000000..b564c1f --- /dev/null +++ b/addons/zylann.translation_editor/tools/icons/icon_load.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/addons/zylann.translation_editor/tools/icons/icon_load.svg.import b/addons/zylann.translation_editor/tools/icons/icon_load.svg.import new file mode 100644 index 0000000..ae1e3b4 --- /dev/null +++ b/addons/zylann.translation_editor/tools/icons/icon_load.svg.import @@ -0,0 +1,29 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon_load.svg-f539f8f7e34373cfd394e1279319f2af.stex" + +[deps] + +source_file="res://addons/zylann.translation_editor/tools/icons/icon_load.svg" +dest_files=[ "res://.import/icon_load.svg-f539f8f7e34373cfd394e1279319f2af.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=false +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/zylann.translation_editor/tools/icons/icon_save.svg b/addons/zylann.translation_editor/tools/icons/icon_save.svg new file mode 100644 index 0000000..d85b7ce --- /dev/null +++ b/addons/zylann.translation_editor/tools/icons/icon_save.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/addons/zylann.translation_editor/tools/icons/icon_save.svg.import b/addons/zylann.translation_editor/tools/icons/icon_save.svg.import new file mode 100644 index 0000000..d56685c --- /dev/null +++ b/addons/zylann.translation_editor/tools/icons/icon_save.svg.import @@ -0,0 +1,29 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon_save.svg-d0c98adea7edb95cd60b5fa2976f910c.stex" + +[deps] + +source_file="res://addons/zylann.translation_editor/tools/icons/icon_save.svg" +dest_files=[ "res://.import/icon_save.svg-d0c98adea7edb95cd60b5fa2976f910c.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=false +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/zylann.translation_editor/tools/plugin.gd b/addons/zylann.translation_editor/tools/plugin.gd new file mode 100644 index 0000000..08f1719 --- /dev/null +++ b/addons/zylann.translation_editor/tools/plugin.gd @@ -0,0 +1,38 @@ +tool +extends EditorPlugin + +var TranslationEditor = load("res://addons/zylann.translation_editor/tools/translation_editor.tscn") + +var _main_control = null + + +func _enter_tree(): + print("Translation editor plugin Enter tree") + + var editor_interface = get_editor_interface() + var base_control = editor_interface.get_base_control() + + _main_control = TranslationEditor.instance() + _main_control.configure_for_godot_integration(base_control) + _main_control.hide() + editor_interface.get_editor_viewport().add_child(_main_control) + + +func _exit_tree(): + # The main control is not freed when the plugin is disabled + _main_control.queue_free() + _main_control = null + + +func has_main_screen(): + return true + + +func get_plugin_name(): + return "Localization" + + +func make_visible(visible): + _main_control.visible = visible + + diff --git a/addons/zylann.translation_editor/tools/po_loader.gd b/addons/zylann.translation_editor/tools/po_loader.gd new file mode 100644 index 0000000..dc0b43f --- /dev/null +++ b/addons/zylann.translation_editor/tools/po_loader.gd @@ -0,0 +1,145 @@ +tool + +static func load_po_translation(folder_path, languages): + var all_strings = {} + + for language in languages: + var filepath = folder_path.plus_file(str(language, ".po")) + + var f = File.new() + var err = f.open(filepath) + if err != OK: + printerr("Could not open file ", filepath, " for read, error ", err) + return null + + var comment = "" + var msgid = "" + var msgstr = "" + var last_is_msgid = false + var ids = [] + var translations = [] + var comments = [] + + while not f.eof_reached(): + var line = f.get_line() + + if line[0] == "#": + comment = str(comment, "\n", line.substr(1, len(line) - 1).strip_edges()) + continue + + var space_index = line.find(" ") + + if line.begins_with("msgid"): + msgid = _parse_msg(line.substr(space_index), len(line) - space_index) + last_is_msgid = true + + elif line.begin_with("msgstr"): + msgstr = _parse_msg(line.substr(space_index), len(line) - space_index) + last_is_msgid = false + + elif line.begin_with('"'): + if last_is_msgid: + msgid = str(msgid, _parse_msg(line)) + else: + msgstr = str(msgstr, _parse_msg(line)) + + elif line == "": + var s = null + if not all_strings.has(msgid): + s = { + "translations": {}, + "comments": "" + } + all_strings[msgid] = s + s.translations[language] = msgstr + if s.comments == "": + s.comments = comment + + comment = "" + msgid = "" + msgstr = "" + + else: + print("Unhandled .po line: ", line) + continue + + return all_strings + + +static func _parse_msg(s): + s = s.strip_edges() + assert(s[0] == '"') + var end = s.rfind('"') + var msg = s.substr(1, end - 1) + return msg.c_unescape().replace('\\"', '"') + + +class _Sorter: + func sort(a, b): + return a[0] < b[0] + + +static func save_po_translations(folder_path, translations, languages_to_save): + var sorter = _Sorter.new() + + 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) + if err != OK: + printerr("Could not open file ", filepath, " for write, error ", err) + continue + + var items = [] + + for id in translations: + var s = translations[id] + if not s.translations.has(language): + continue + items.append([id, s.translations[language], s.comments]) + + items.sort_custom(sorter, "sort") + + for item in items: + + var comment = item[2] + if comment != "": + var comment_lines = comment.split("\n") + for line in comment_lines: + f.store_line(str("# ", line)) + + _write_msg(f, "msgid", item[0]) + _write_msg(f, "msgstr", item[1]) + + f.store_line("") + + f.close() + + +static func _write_msg(f, msgtype, msg): + var lines = msg.split("\n") + if len(lines) > 1: + for i in range(0, len(lines) - 1): + lines[i] = str(lines[i], "\n") + + # This is just to avoid too long lines +# if len(lines) > 1: +# var rlines = [] +# for i in len(rlines): +# var line = rlines[i] +# var maxlen = 78 +# if i == 0: +# maxlen -= len(msgtype) + 1 +# while len(line) > maxlen: +# line = line.substr(0, maxlen) +# rlines.append(line) +# rlines.append(line) +# lines = rlines + + for i in len(lines): + lines[i] = lines[i].c_escape().replace('"', '\\"') + + f.store_line(str(msgtype, " \"", lines[0], "\"")) + for i in range(1, len(lines)): + f.store_line(str(" \"", lines[i], "\"")) diff --git a/addons/zylann.translation_editor/tools/string_edition_dialog.gd b/addons/zylann.translation_editor/tools/string_edition_dialog.gd new file mode 100644 index 0000000..d6dfdd8 --- /dev/null +++ b/addons/zylann.translation_editor/tools/string_edition_dialog.gd @@ -0,0 +1,73 @@ +tool +extends WindowDialog + +signal submitted(str_id, prev_str_id) + +onready var _line_edit = get_node("VBoxContainer/LineEdit") +onready var _ok_button = get_node("VBoxContainer/Buttons/OkButton") +onready var _hint_label = get_node("VBoxContainer/HintLabel") + +var _validator_func = null +var _prev_str_id = null + + +func set_replaced_str_id(str_id): + assert(typeof(str_id) == TYPE_STRING or str_id == null) + _prev_str_id = str_id + if typeof(str_id) == TYPE_STRING: + _line_edit.text = str_id + + +func set_validator(f): + _validator_func = f + + +func _notification(what): + if what == NOTIFICATION_VISIBILITY_CHANGED: + if visible: + if _prev_str_id == null: + window_title = "New string ID" + else: + window_title = str("Replace `", _prev_str_id, "`") + _line_edit.grab_focus() + _validate() + + +func _on_LineEdit_text_changed(new_text): + _validate() + + +func _validate(): + 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) + assert(typeof(res) == TYPE_BOOL or typeof(res) == TYPE_STRING) + if typeof(res) != TYPE_BOOL or res == false: + hint_message = res + valid = false + + _ok_button.disabled = not valid + _hint_label.text = hint_message + # Note: hiding the label would shift up other controls in the container + + +func _on_LineEdit_text_entered(new_text): + submit() + + +func _on_OkButton_pressed(): + submit() + + +func _on_CancelButton_pressed(): + hide() + + +func submit(): + var s = _line_edit.text.strip_edges() + emit_signal("submitted", s, _prev_str_id) + hide() + diff --git a/addons/zylann.translation_editor/tools/string_edition_dialog.tscn b/addons/zylann.translation_editor/tools/string_edition_dialog.tscn new file mode 100644 index 0000000..5181b96 --- /dev/null +++ b/addons/zylann.translation_editor/tools/string_edition_dialog.tscn @@ -0,0 +1,185 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/zylann.translation_editor/tools/string_edition_dialog.gd" type="Script" id=1] + +[node name="StringEditionDialog" type="WindowDialog"] + +visible = false +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 118.0 +margin_top = 154.0 +margin_right = 446.0 +margin_bottom = 259.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 = 1 +popup_exclusive = false +window_title = "New string" +resizable = false +script = ExtResource( 1 ) + +[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 = -32.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="HintLabel" type="Label" parent="VBoxContainer" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 312.0 +margin_bottom = 14.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 = "Already existing" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="LineEdit" type="LineEdit" parent="VBoxContainer" index="1"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 18.0 +margin_right = 312.0 +margin_bottom = 42.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 = 1 +size_flags_vertical = 1 +focus_mode = 2 +context_menu_enabled = true +placeholder_alpha = 0.6 +caret_blink = false +caret_blink_speed = 0.65 +caret_position = 0 + +[node name="Spacer" type="Control" 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 = 312.0 +margin_bottom = 54.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="3"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 58.0 +margin_right = 312.0 +margin_bottom = 78.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="OkButton" 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 = 48.0 +margin_right = 148.0 +margin_bottom = 20.0 +rect_min_size = Vector2( 100, 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 = "OK" +flat = false +align = 1 +_sections_unfolded = [ "Rect" ] + +[node name="CancelButton" 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 = 164.0 +margin_right = 264.0 +margin_bottom = 20.0 +rect_min_size = Vector2( 100, 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 +_sections_unfolded = [ "Rect" ] + +[connection signal="text_changed" from="VBoxContainer/LineEdit" to="." method="_on_LineEdit_text_changed"] + +[connection signal="text_entered" from="VBoxContainer/LineEdit" to="." method="_on_LineEdit_text_entered"] + +[connection signal="pressed" from="VBoxContainer/Buttons/OkButton" to="." method="_on_OkButton_pressed"] + +[connection signal="pressed" from="VBoxContainer/Buttons/CancelButton" to="." method="_on_CancelButton_pressed"] + + diff --git a/addons/zylann.translation_editor/tools/translation_editor.gd b/addons/zylann.translation_editor/tools/translation_editor.gd new file mode 100644 index 0000000..2ce4adb --- /dev/null +++ b/addons/zylann.translation_editor/tools/translation_editor.gd @@ -0,0 +1,213 @@ +tool +extends Panel + +const CsvLoader = preload("csv_loader.gd") +const PoLoader = preload("po_loader.gd") +const StringEditionDialog = preload("string_edition_dialog.tscn") + +const MENU_FILE_OPEN = 0 +const MENU_FILE_SAVE = 1 +const MENU_FILE_SAVE_AS = 2 + +onready var _file_menu = get_node("VBoxContainer/MenuBar/FileMenu") +onready var _edit_menu = get_node("VBoxContainer/MenuBar/EditMenu") +onready var _string_list = get_node("VBoxContainer/Main/LeftPane/StringList") +onready var _translation_tab_container = \ + get_node("VBoxContainer/Main/RightPane/VSplitContainer/TranslationTabContainer") +onready var _notes_edit = get_node("VBoxContainer/Main/RightPane/VSplitContainer/VBoxContainer/NotesEdit") + +var _string_edit_dialog = null +var _open_dialog = null +var _save_dialog = null + +# This is set when integrated as a Godot plugin +var _base_control = null + +var _data = null +# TODO Make this a config of some sort +var _languages = ["en", "fr"] +var _current_file = null + +var _translation_edits = {} + + +func _ready(): + # I don't want any of this to run in the edited scene (because `tool`)... + if Engine.editor_hint and get_parent() is Viewport: + return + + _file_menu.get_popup().add_item("Open...", MENU_FILE_OPEN) + _file_menu.get_popup().add_item("Save", MENU_FILE_SAVE) + _file_menu.get_popup().add_item("Save as...", MENU_FILE_SAVE_AS) + _file_menu.get_popup().connect("id_pressed", self, "_on_FileMenu_id_pressed") + + _edit_menu.get_popup().connect("id_pressed", self, "_on_EditMenu_id_pressed") + + var dialogs_parent = self + if _base_control != null: + dialogs_parent = _base_control + self_modulate = Color(0,0,0,0) + + _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) + + _save_dialog = FileDialog.new() + _save_dialog.window_title = "Save translations" + _save_dialog.add_filter("*.csv ; CSV files") + _save_dialog.add_filter("*.po ; Gettext files") + _save_dialog.mode = FileDialog.MODE_SAVE_FILE + _save_dialog.connect("file_selected", self, "_on_SaveDialog_file_selected") + dialogs_parent.add_child(_save_dialog) + + _string_edit_dialog = StringEditionDialog.instance() + _string_edit_dialog.set_validator(funcref(self, "_validate_new_string_id")) + dialogs_parent.add_child(_string_edit_dialog) + + +func configure_for_godot_integration(base_control): + # You have to call this before adding to the tree + assert(not is_inside_tree()) + _base_control = base_control + + +func _on_FileMenu_id_pressed(id): + match id: + MENU_FILE_OPEN: + _open_dialog.popup_centered_ratio() + MENU_FILE_SAVE: + if _current_file == null: + _save_dialog.popup_centered_ratio() + else: + save_file(_current_file) + MENU_FILE_SAVE_AS: + _save_dialog.popup_centered_ratio() + + +func _on_EditMenu_id_pressed(id): + pass + + +func _on_OpenDialog_file_selected(filepath): + load_file(filepath) + + +func _on_SaveDialog_file_selected(filepath): + save_file(filepath) + + +func load_file(filepath): + var ext = filepath.get_extension() + if ext == "po": + _data = PoLoader.load_po_translation(filepath) + elif ext == "csv": + _data = CsvLoader.load_csv_translation(filepath) + else: + printerr("Unknown file format, cannot load ", filepath) + return + + _translation_edits.clear() + + for i in _translation_tab_container.get_child_count(): + var child = _translation_tab_container.get_child(i) + if child is TextEdit: + child.queue_free() + + for language in _languages: + var edit = TextEdit.new() + 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 + + refresh_list() + _current_file = filepath + + +func save_file(filepath): + var ext = filepath.get_extension() + if ext == "po": + PoLoader.save_po_translations(filepath.get_base_dir(), _data, _languages) + elif ext == "csv": + CsvLoader.save_csv_translation(_data) + else: + printerr("Unknown file format, cannot save ", filepath) + + +func refresh_list(): + _string_list.clear() + var ordered_ids = _data.keys() + ordered_ids.sort() + for id in ordered_ids: + #var i = _string_list.get_item_count() + _string_list.add_item(id) + + +func _on_StringList_item_selected(index): + var str_id = _string_list.get_item_text(index) + var s = _data[str_id] + for language in _languages: + var e = _translation_edits[language] + if s.translations.has(language): + e.text = s.translations[language] + else: + e.text = "" + _notes_edit.text = s.comments + + +func _on_AddButton_pressed(): + _string_edit_dialog.set_replaced_str_id(null) + _string_edit_dialog.popup_centered() + + +func _on_RemoveButton_pressed(): + # TODO Remove string with confirmation + pass + + +func _on_RenameButton_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]) + _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: + add_new_string(str_id) + else: + rename_string(prev_str_id, str_id) + + +func _validate_new_string_id(str_id): + if _data.has(str_id): + return "Already existing" + return true + + +func add_new_string(strid): + assert(not _data.has(strid)) + var s = { + "translations": {}, + "comments": "" + } + _data[strid] = s + _string_list.add_item(strid) + + +func rename_string(old_strid, new_strid): + assert(_data.has(old_strid)) + var s = _data[old_strid] + _data.erase(old_strid) + _data[new_strid] = s + for i in _string_list.get_item_count(): + if _string_list.get_item_text(i) == old_strid: + _string_list.set_item_text(i, new_strid) + break + diff --git a/addons/zylann.translation_editor/tools/translation_editor.tscn b/addons/zylann.translation_editor/tools/translation_editor.tscn new file mode 100644 index 0000000..ca00660 --- /dev/null +++ b/addons/zylann.translation_editor/tools/translation_editor.tscn @@ -0,0 +1,440 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/zylann.translation_editor/tools/translation_editor.gd" type="Script" id=1] + +[node name="TranslationEditor" type="Panel" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 1.0 +anchor_bottom = 1.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 = 3 +script = ExtResource( 1 ) +_sections_unfolded = [ "Margin", "Size Flags" ] + +[node name="VBoxContainer" type="VBoxContainer" parent="." index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 4.0 +margin_top = 4.0 +margin_right = -4.0 +margin_bottom = -4.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="MenuBar" type="HBoxContainer" parent="VBoxContainer" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 792.0 +margin_bottom = 20.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="FileMenu" type="MenuButton" parent="VBoxContainer/MenuBar" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 35.0 +margin_bottom = 20.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 = 1 +toggle_mode = false +action_mode = 0 +enabled_focus_mode = 0 +shortcut = null +group = null +text = "File" +flat = true +align = 1 +items = [ ] + +[node name="EditMenu" type="MenuButton" parent="VBoxContainer/MenuBar" index="1"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 39.0 +margin_right = 75.0 +margin_bottom = 20.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 = 1 +toggle_mode = false +action_mode = 0 +enabled_focus_mode = 0 +shortcut = null +group = null +text = "Edit" +flat = true +align = 1 +items = [ ] + +[node name="Main" type="HSplitContainer" parent="VBoxContainer" index="1"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 24.0 +margin_right = 792.0 +margin_bottom = 454.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 = 3 +split_offset = 100 +collapsed = false +dragger_visibility = 0 +_sections_unfolded = [ "Size Flags" ] + +[node name="LeftPane" type="VBoxContainer" parent="VBoxContainer/Main" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 273.0 +margin_bottom = 430.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="Search" type="LineEdit" parent="VBoxContainer/Main/LeftPane" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 273.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 = 1 +size_flags_vertical = 1 +focus_mode = 2 +context_menu_enabled = true +placeholder_alpha = 0.6 +caret_blink = false +caret_blink_speed = 0.65 +caret_position = 0 + +[node name="StringList" type="ItemList" parent="VBoxContainer/Main/LeftPane" index="1"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 28.0 +margin_right = 273.0 +margin_bottom = 406.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 +items = [ ] +select_mode = 0 +allow_reselect = false +icon_mode = 1 +fixed_icon_size = Vector2( 0, 0 ) +_sections_unfolded = [ "Size Flags" ] + +[node name="StringListActions" type="HBoxContainer" parent="VBoxContainer/Main/LeftPane" index="2"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 410.0 +margin_right = 273.0 +margin_bottom = 430.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="AddButton" type="Button" parent="VBoxContainer/Main/LeftPane/StringListActions" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 37.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 = "Add" +flat = false +align = 1 + +[node name="RemoveButton" type="Button" parent="VBoxContainer/Main/LeftPane/StringListActions" index="1"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 41.0 +margin_right = 105.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 = "Remove" +flat = false +align = 1 + +[node name="RenameButton" type="Button" parent="VBoxContainer/Main/LeftPane/StringListActions" index="2"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 109.0 +margin_right = 173.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 = "Rename" +flat = false +align = 1 + +[node name="RightPane" type="VBoxContainer" parent="VBoxContainer/Main" index="1"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 285.0 +margin_right = 792.0 +margin_bottom = 430.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="VSplitContainer" type="VSplitContainer" parent="VBoxContainer/Main/RightPane" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 507.0 +margin_bottom = 430.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 = 3 +split_offset = 250 +collapsed = false +dragger_visibility = 0 +_sections_unfolded = [ "Size Flags" ] + +[node name="TranslationTabContainer" type="TabContainer" parent="VBoxContainer/Main/RightPane/VSplitContainer" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 507.0 +margin_bottom = 286.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 = 1 +tab_align = 0 +tabs_visible = true + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/Main/RightPane/VSplitContainer" index="1"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 298.0 +margin_right = 507.0 +margin_bottom = 430.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/Main/RightPane/VSplitContainer/VBoxContainer" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 507.0 +margin_bottom = 14.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 = "Notes" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="NotesEdit" type="TextEdit" parent="VBoxContainer/Main/RightPane/VSplitContainer/VBoxContainer" index="1"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 18.0 +margin_right = 507.0 +margin_bottom = 132.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 = 3 +text = "" +readonly = false +highlight_current_line = false +syntax_highlighting = false +show_line_numbers = false +highlight_all_occurrences = false +override_selected_font_color = false +context_menu_enabled = true +smooth_scrolling = false +v_scroll_speed = 80.0 +hiding_enabled = 0 +wrap_lines = false +caret_block_mode = false +caret_blink = false +caret_blink_speed = 0.65 +caret_moving_by_right_click = true +_sections_unfolded = [ "Size Flags" ] + +[node name="StatusBar" type="HBoxContainer" parent="VBoxContainer" index="2"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 458.0 +margin_right = 792.0 +margin_bottom = 472.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/StatusBar" index="0"] + +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 39.0 +margin_bottom = 14.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 = "Status" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[connection signal="item_selected" from="VBoxContainer/Main/LeftPane/StringList" to="." method="_on_StringList_item_selected"] + +[connection signal="pressed" from="VBoxContainer/Main/LeftPane/StringListActions/AddButton" to="." method="_on_AddButton_pressed"] + +[connection signal="pressed" from="VBoxContainer/Main/LeftPane/StringListActions/RemoveButton" to="." method="_on_RemoveButton_pressed"] + +[connection signal="pressed" from="VBoxContainer/Main/LeftPane/StringListActions/RenameButton" to="." method="_on_RenameButton_pressed"] + + diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..a0b64ee Binary files /dev/null and b/icon.png differ diff --git a/icon.png.import b/icon.png.import new file mode 100644 index 0000000..0041ef8 --- /dev/null +++ b/icon.png.import @@ -0,0 +1,29 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" + +[deps] + +source_file="res://icon.png" +dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..962244a --- /dev/null +++ b/project.godot @@ -0,0 +1,18 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=3 + +[application] + +config/name="TranslationEditor" +config/icon="res://icon.png" + +[rendering] + +environment/default_environment="res://default_env.tres"