]> Untitled Git - wolf-seeking-sheep.git/blob - addons/dialogic/Editor/Settings/settings_translation.gd
Initial Godot project with Dialogic 2.0-Alpha-17
[wolf-seeking-sheep.git] / addons / dialogic / Editor / Settings / settings_translation.gd
1 @tool
2 extends DialogicSettingsPage
3
4 ## Settings tab that allows enabeling and updating translation csv-files.
5
6
7 enum TranslationModes {PER_PROJECT, PER_TIMELINE, NONE}
8 enum SaveLocationModes {INSIDE_TRANSLATION_FOLDER, NEXT_TO_TIMELINE, NONE}
9
10 var loading := false
11 @onready var settings_editor: Control = find_parent('Settings')
12
13 ## The default CSV filename that contains the translations for character
14 ## properties.
15 const DEFAULT_CHARACTER_CSV_NAME := "dialogic_character_translations.csv"
16 ## The default CSV filename that contains the translations for timelines.
17 ## Only used when all timelines are supposed to be translated in one file.
18 const DEFAULT_TIMELINE_CSV_NAME := "dialogic_timeline_translations.csv"
19
20 const DEFAULT_GLOSSARY_CSV_NAME := "dialogic_glossary_translations.csv"
21
22 const _USED_LOCALES_SETTING := "dialogic/translation/locales"
23
24 ## Contains translation changes that were made during the last update.
25
26 ## Unique locales that will be set after updating the CSV files.
27 var _unique_locales := []
28
29 func _get_icon() -> Texture2D:
30         return get_theme_icon("Translation", "EditorIcons")
31
32
33 func _is_feature_tab() -> bool:
34         return true
35
36
37 func _ready() -> void:
38         %TransEnabled.toggled.connect(store_changes)
39         %OrigLocale.get_suggestions_func = get_locales
40         %OrigLocale.resource_icon = get_theme_icon("Translation", "EditorIcons")
41         %OrigLocale.value_changed.connect(store_changes)
42         %TestingLocale.get_suggestions_func = get_locales
43         %TestingLocale.resource_icon = get_theme_icon("Translation", "EditorIcons")
44         %TestingLocale.value_changed.connect(store_changes)
45         %TransFolderPicker.value_changed.connect(store_changes)
46         %AddSeparatorEnabled.toggled.connect(store_changes)
47
48         %SaveLocationMode.item_selected.connect(store_changes)
49         %TransMode.item_selected.connect(store_changes)
50
51         %UpdateCsvFiles.pressed.connect(_on_update_translations_pressed)
52         %UpdateCsvFiles.icon = get_theme_icon("Add", "EditorIcons")
53
54         %CollectTranslations.pressed.connect(collect_translations)
55         %CollectTranslations.icon = get_theme_icon("File", "EditorIcons")
56
57         %TransRemove.pressed.connect(_on_erase_translations_pressed)
58         %TransRemove.icon = get_theme_icon("Remove", "EditorIcons")
59
60         %UpdateConfirmationDialog.add_button("Keep old & Generate new", false, "generate_new")
61
62         %UpdateConfirmationDialog.custom_action.connect(_on_custom_action)
63
64         _verify_translation_file()
65
66
67 func _on_custom_action(action: String) -> void:
68         if action == "generate_new":
69                 update_csv_files()
70
71
72 func _refresh() -> void:
73         loading = true
74
75         %TransEnabled.button_pressed = ProjectSettings.get_setting('dialogic/translation/enabled', false)
76         %TranslationSettings.visible = %TransEnabled.button_pressed
77         %OrigLocale.set_value(ProjectSettings.get_setting('dialogic/translation/original_locale', TranslationServer.get_tool_locale()))
78         %TransMode.select(ProjectSettings.get_setting('dialogic/translation/file_mode', 1))
79         %TransFolderPicker.set_value(ProjectSettings.get_setting('dialogic/translation/translation_folder', ''))
80         %TestingLocale.set_value(ProjectSettings.get_setting('internationalization/locale/test', ''))
81         %AddSeparatorEnabled.button_pressed = ProjectSettings.get_setting('dialogic/translation/add_separator', false)
82
83         _verify_translation_file()
84
85         loading = false
86
87
88 func store_changes(_fake_arg: Variant = null, _fake_arg2: Variant = null) -> void:
89         if loading:
90                 return
91
92         _verify_translation_file()
93
94         ProjectSettings.set_setting('dialogic/translation/enabled', %TransEnabled.button_pressed)
95         %TranslationSettings.visible = %TransEnabled.button_pressed
96         ProjectSettings.set_setting('dialogic/translation/original_locale', %OrigLocale.current_value)
97         ProjectSettings.set_setting('dialogic/translation/file_mode', %TransMode.selected)
98         ProjectSettings.set_setting('dialogic/translation/translation_folder', %TransFolderPicker.current_value)
99         ProjectSettings.set_setting('internationalization/locale/test', %TestingLocale.current_value)
100         ProjectSettings.set_setting('dialogic/translation/save_mode', %SaveLocationMode.selected)
101         ProjectSettings.set_setting('dialogic/translation/add_separator', %AddSeparatorEnabled.button_pressed)
102         ProjectSettings.save()
103
104
105 ## Checks whether the translation folder path is required.
106 ## If it is, disables the "Update CSV files" button and shows a warning.
107 ##
108 ## The translation folder path is required when either of the following is true:
109 ## - The translation mode is set to "Per Project".
110 ## - The save location mode is set to "Inside Translation Folder".
111 func _verify_translation_file() -> void:
112         var translation_folder: String = %TransFolderPicker.current_value
113         var file_mode: TranslationModes = %TransMode.selected
114
115         if file_mode == TranslationModes.PER_PROJECT:
116                 %SaveLocationMode.disabled = true
117         else:
118                 %SaveLocationMode.disabled = false
119
120         var valid_translation_folder := (!translation_folder.is_empty()
121                 and DirAccess.dir_exists_absolute(translation_folder))
122
123         %UpdateCsvFiles.disabled = not valid_translation_folder
124
125         var status_message := ""
126
127         if not valid_translation_folder:
128                 status_message += "⛔ Requires valid translation folder to translate character names"
129
130                 if file_mode == TranslationModes.PER_PROJECT:
131                         status_message += " and the project CSV file."
132                 else:
133                         status_message += "."
134
135         %StatusMessage.text = status_message
136
137
138 func get_locales(_filter: String) -> Dictionary:
139         var suggestions := {}
140         suggestions['Default'] = {'value':'', 'tooltip':"Will use the fallback locale set in the project settings."}
141         suggestions[TranslationServer.get_tool_locale()] = {'value':TranslationServer.get_tool_locale()}
142
143         var used_locales: Array = ProjectSettings.get_setting(_USED_LOCALES_SETTING, TranslationServer.get_all_languages())
144
145         for locale: String in used_locales:
146                 var language_name := TranslationServer.get_language_name(locale)
147
148                 # Invalid locales return an empty String.
149                 if language_name.is_empty():
150                         continue
151
152                 suggestions[locale] = { 'value': locale, 'tooltip': language_name }
153
154         return suggestions
155
156
157 func _on_update_translations_pressed() -> void:
158         var save_mode: SaveLocationModes = %SaveLocationMode.selected
159         var file_mode: TranslationModes = %TransMode.selected
160         var translation_folder: String = %TransFolderPicker.current_value
161
162         var old_save_mode: SaveLocationModes = ProjectSettings.get_setting('dialogic/translation/intern/save_mode', save_mode)
163         var old_file_mode: TranslationModes = ProjectSettings.get_setting('dialogic/translation/intern/file_mode', file_mode)
164         var old_translation_folder: String = ProjectSettings.get_setting('dialogic/translation/intern/translation_folder', translation_folder)
165
166         if (old_save_mode == save_mode
167         and old_file_mode == file_mode
168         and old_translation_folder == translation_folder):
169                 update_csv_files()
170                 return
171
172         %UpdateConfirmationDialog.popup_centered()
173
174
175 ## Used by the dialog to inform that the settings were changed.
176 func _delete_and_update() -> void:
177         erase_translations()
178         update_csv_files()
179
180
181 ## Creates or updates the glossary CSV files.
182 func _handle_glossary_translation(
183         csv_data: CsvUpdateData,
184         save_location_mode: SaveLocationModes,
185         translation_mode: TranslationModes,
186         translation_folder_path: String,
187         orig_locale: String) -> void:
188
189         var glossary_csv: DialogicCsvFile = null
190         var glossary_paths: Array = ProjectSettings.get_setting('dialogic/glossary/glossary_files', [])
191         var add_separator_lines: bool = ProjectSettings.get_setting('dialogic/translation/add_separator', false)
192
193         for glossary_path: String in glossary_paths:
194
195                 if glossary_csv == null:
196                         var csv_name := ""
197
198                         # Get glossary CSV file name.
199                         match translation_mode:
200                                 TranslationModes.PER_PROJECT:
201                                         csv_name = DEFAULT_GLOSSARY_CSV_NAME
202
203                                 TranslationModes.PER_TIMELINE:
204                                         var glossary_name: String = glossary_path.trim_suffix('.tres')
205                                         var path_parts := glossary_name.split("/")
206                                         var file_name := path_parts[-1]
207                                         csv_name = "dialogic_" + file_name + '_translation.csv'
208
209                         var glossary_csv_path := ""
210                         # Get glossary CSV file path.
211                         match save_location_mode:
212                                 SaveLocationModes.INSIDE_TRANSLATION_FOLDER:
213                                         glossary_csv_path = translation_folder_path.path_join(csv_name)
214
215                                 SaveLocationModes.NEXT_TO_TIMELINE:
216                                         glossary_csv_path = glossary_path.get_base_dir().path_join(csv_name)
217
218                         # Create or update glossary CSV file.
219                         glossary_csv = DialogicCsvFile.new(glossary_csv_path, orig_locale, add_separator_lines)
220
221                         if (glossary_csv.is_new_file):
222                                 csv_data.new_glossaries += 1
223                         else:
224                                 csv_data.updated_glossaries += 1
225
226                 var glossary: DialogicGlossary = load(glossary_path)
227                 glossary_csv.collect_lines_from_glossary(glossary)
228                 glossary_csv.add_translation_keys_to_glossary(glossary)
229                 ResourceSaver.save(glossary)
230
231                 #If per-file mode is used, save this csv and begin a new one
232                 if translation_mode == TranslationModes.PER_TIMELINE:
233                         glossary_csv.update_csv_file_on_disk()
234                         glossary_csv = null
235
236         # If a Per-Project glossary is still open, we need to save it.
237         if glossary_csv != null:
238                 glossary_csv.update_csv_file_on_disk()
239                 glossary_csv = null
240
241
242 ## Keeps information about the amount of new and updated CSV rows and what
243 ## resources were populated with translation IDs.
244 ## The final data can be used to display a status message.
245 class CsvUpdateData:
246         var new_events := 0
247         var updated_events := 0
248
249         var new_timelines := 0
250         var updated_timelines := 0
251
252         var new_names := 0
253         var updated_names := 0
254
255         var new_glossaries := 0
256         var updated_glossaries := 0
257
258         var new_glossary_entries := 0
259         var updated_glossary_entries := 0
260
261
262 func update_csv_files() -> void:
263         _unique_locales = []
264         var orig_locale: String = ProjectSettings.get_setting('dialogic/translation/original_locale', '').strip_edges()
265         var save_location_mode: SaveLocationModes = ProjectSettings.get_setting('dialogic/translation/save_mode', SaveLocationModes.NEXT_TO_TIMELINE)
266         var translation_mode: TranslationModes = ProjectSettings.get_setting('dialogic/translation/file_mode', TranslationModes.PER_PROJECT)
267         var translation_folder_path: String = ProjectSettings.get_setting('dialogic/translation/translation_folder', 'res://')
268         var add_separator_lines: bool = ProjectSettings.get_setting('dialogic/translation/add_separator', false)
269
270         var csv_data := CsvUpdateData.new()
271
272         if orig_locale.is_empty():
273                 orig_locale = ProjectSettings.get_setting('internationalization/locale/fallback')
274
275         ProjectSettings.set_setting('dialogic/translation/intern/save_mode', save_location_mode)
276         ProjectSettings.set_setting('dialogic/translation/intern/file_mode', translation_mode)
277         ProjectSettings.set_setting('dialogic/translation/intern/translation_folder', translation_folder_path)
278
279         var current_timeline := _close_active_timeline()
280
281         var csv_per_project: DialogicCsvFile = null
282         var per_project_csv_path := translation_folder_path.path_join(DEFAULT_TIMELINE_CSV_NAME)
283
284         if translation_mode == TranslationModes.PER_PROJECT:
285                 csv_per_project = DialogicCsvFile.new(per_project_csv_path, orig_locale, add_separator_lines)
286
287                 if (csv_per_project.is_new_file):
288                         csv_data.new_timelines += 1
289                 else:
290                         csv_data.updated_timelines += 1
291
292         # Iterate over all timelines.
293         # Create or update CSV files.
294         # Transform the timeline into translatable lines and collect into the CSV file.
295         for timeline_path: String in DialogicResourceUtil.list_resources_of_type('.dtl'):
296                 var csv_file: DialogicCsvFile = csv_per_project
297
298                 # Swap the CSV file to the Per Timeline one.
299                 if translation_mode == TranslationModes.PER_TIMELINE:
300                         var per_timeline_path: String = timeline_path.trim_suffix('.dtl')
301                         var path_parts := per_timeline_path.split("/")
302                         var timeline_name: String = path_parts[-1]
303
304                         # Adjust the file path to the translation location mode.
305                         if save_location_mode == SaveLocationModes.INSIDE_TRANSLATION_FOLDER:
306                                 var prefixed_timeline_name := "dialogic_" + timeline_name
307                                 per_timeline_path = translation_folder_path.path_join(prefixed_timeline_name)
308
309
310                         per_timeline_path += '_translation.csv'
311                         csv_file = DialogicCsvFile.new(per_timeline_path, orig_locale, false)
312                         csv_data.new_timelines += 1
313
314                 # Load and process timeline, turn events into resources.
315                 var timeline: DialogicTimeline = load(timeline_path)
316
317                 if timeline.events.size() == 0:
318                         print_rich("[color=yellow]Empty timeline, skipping: " + timeline_path + "[/color]")
319                         continue
320
321                 timeline.process()
322
323                 # Collect timeline into CSV.
324                 csv_file.collect_lines_from_timeline(timeline)
325
326                 # in case new translation_id's were added, we save the timeline again
327                 timeline.set_meta("timeline_not_saved", true)
328                 ResourceSaver.save(timeline, timeline_path)
329
330                 if translation_mode == TranslationModes.PER_TIMELINE:
331                         csv_file.update_csv_file_on_disk()
332
333                 csv_data.new_events += csv_file.new_rows
334                 csv_data.updated_events += csv_file.updated_rows
335
336         _handle_glossary_translation(
337                 csv_data,
338                 save_location_mode,
339                 translation_mode,
340                 translation_folder_path,
341                 orig_locale
342         )
343
344         _handle_character_names(
345                 csv_data,
346                 orig_locale,
347                 translation_folder_path,
348                 add_separator_lines
349         )
350
351         if translation_mode == TranslationModes.PER_PROJECT:
352                 csv_per_project.update_csv_file_on_disk()
353
354         _silently_open_timeline(current_timeline)
355
356         # Trigger reimport.
357         find_parent('EditorView').plugin_reference.get_editor_interface().get_resource_filesystem().scan_sources()
358
359         var status_message := "Events   created {new_events}   found {updated_events}
360                 Names  created {new_names}   found {updated_names}
361                 CSVs      created {new_timelines}   found {updated_timelines}
362                 Glossary  created {new_glossaries}   found {updated_glossaries}
363                 Entries   created {new_glossary_entries}   found {updated_glossary_entries}"
364
365         var status_message_args := {
366                 'new_events': csv_data.new_events,
367                 'updated_events': csv_data.updated_events,
368                 'new_timelines': csv_data.new_timelines,
369                 'updated_timelines': csv_data.updated_timelines,
370                 'new_glossaries': csv_data.new_glossaries,
371                 'updated_glossaries': csv_data.updated_glossaries,
372                 'new_names': csv_data.new_names,
373                 'updated_names': csv_data.updated_names,
374                 'new_glossary_entries': csv_data.new_glossary_entries,
375                 'updated_glossary_entries': csv_data.updated_glossary_entries,
376         }
377
378         %StatusMessage.text = status_message.format(status_message_args)
379         ProjectSettings.set_setting(_USED_LOCALES_SETTING, _unique_locales)
380
381
382 ## Iterates over all character resource files and creates or updates CSV files
383 ## that contain the translations for character properties.
384 ## This will save each character resource file to disk.
385 func _handle_character_names(
386                 csv_data: CsvUpdateData,
387                 original_locale: String,
388                 translation_folder_path: String,
389                 add_separator_lines: bool) -> void:
390         var names_csv_path := translation_folder_path.path_join(DEFAULT_CHARACTER_CSV_NAME)
391         var character_name_csv: DialogicCsvFile = DialogicCsvFile.new(names_csv_path,
392                  original_locale,
393                  add_separator_lines
394         )
395
396         var all_characters := {}
397
398         for character_path: String in DialogicResourceUtil.list_resources_of_type('.dch'):
399                 var character: DialogicCharacter = load(character_path)
400
401                 if character._translation_id.is_empty():
402                         csv_data.new_names += 1
403
404                 else:
405                         csv_data.updated_names += 1
406
407                 var translation_id := character.get_set_translation_id()
408                 all_characters[translation_id] = character
409
410                 ResourceSaver.save(character)
411
412         character_name_csv.collect_lines_from_characters(all_characters)
413         character_name_csv.update_csv_file_on_disk()
414
415
416 func collect_translations() -> void:
417         var translation_files := []
418         var translation_mode: TranslationModes = ProjectSettings.get_setting('dialogic/translation/file_mode', TranslationModes.PER_PROJECT)
419
420         if translation_mode == TranslationModes.PER_TIMELINE:
421
422                 for timeline_path: String in DialogicResourceUtil.list_resources_of_type('.translation'):
423
424                         for file: String in DialogicUtil.listdir(timeline_path.get_base_dir()):
425                                 file = timeline_path.get_base_dir().path_join(file)
426
427                                 if file.ends_with('.translation'):
428
429                                         if not file in translation_files:
430                                                 translation_files.append(file)
431
432         if translation_mode == TranslationModes.PER_PROJECT:
433                 var translation_folder: String = ProjectSettings.get_setting('dialogic/translation/translation_folder', 'res://')
434
435                 for file: String in DialogicUtil.listdir(translation_folder):
436                         file = translation_folder.path_join(file)
437
438                         if file.ends_with('.translation'):
439
440                                 if not file in translation_files:
441                                         translation_files.append(file)
442
443         var all_translation_files: Array = ProjectSettings.get_setting('internationalization/locale/translations', [])
444         var orig_file_amount := len(all_translation_files)
445
446         # This array keeps track of valid translation file paths.
447         var found_file_paths := []
448         var removed_translation_files := 0
449
450         for file_path: String in translation_files:
451                 # If the file path is not valid, we must clean it up.
452                 if ResourceLoader.exists(file_path):
453                         found_file_paths.append(file_path)
454                 else:
455                         removed_translation_files += 1
456                         continue
457
458                 if not file_path in all_translation_files:
459                         all_translation_files.append(file_path)
460
461                 var path_without_suffix := file_path.trim_suffix('.translation')
462                 var locale_part := path_without_suffix.split(".")[-1]
463                 _collect_locale(locale_part)
464
465
466         var valid_translation_files := PackedStringArray(all_translation_files)
467         ProjectSettings.set_setting('internationalization/locale/translations', valid_translation_files)
468         ProjectSettings.save()
469
470         %StatusMessage.text = (
471                 "Added translation files: " + str(len(all_translation_files)-orig_file_amount)
472                 + "\nRemoved translation files: " + str(removed_translation_files)
473                 + "\nTotal translation files: " + str(len(all_translation_files)))
474
475
476 func _on_erase_translations_pressed() -> void:
477         %EraseConfirmationDialog.popup_centered()
478
479
480 ## Deletes translation files generated by [param csv_name].
481 ## The [param csv_name] may not contain the file extension (.csv).
482 ##
483 ## Returns a vector, value 1 is amount of deleted translation files.
484 ## Value
485 func delete_translations_files(translation_files: Array, csv_name: String) -> int:
486         var deleted_files := 0
487
488         for file_path: String in DialogicResourceUtil.list_resources_of_type('.translation'):
489                 var base_name: String = file_path.get_basename()
490                 var path_parts := base_name.split("/")
491                 var translation_name: String = path_parts[-1]
492
493                 if translation_name.begins_with(csv_name):
494
495                         if OK == DirAccess.remove_absolute(file_path):
496                                 var project_translation_file_index := translation_files.find(file_path)
497
498                                 if project_translation_file_index > -1:
499                                         translation_files.remove_at(project_translation_file_index)
500
501                                 deleted_files += 1
502                                 print_rich("[color=green]Deleted translation file: " + file_path + "[/color]")
503                         else:
504                                 print_rich("[color=yellow]Failed to delete translation file: " + file_path + "[/color]")
505
506
507         return deleted_files
508
509
510 ## Iterates over all timelines and deletes their CSVs and timeline
511 ## translation IDs.
512 ## Deletes the Per-Project CSV file and the character name CSV file.
513 func erase_translations() -> void:
514         var files: PackedStringArray = ProjectSettings.get_setting('internationalization/locale/translations', [])
515         var translation_files := Array(files)
516         ProjectSettings.set_setting(_USED_LOCALES_SETTING, [])
517
518         var deleted_csv_files := 0
519         var deleted_translation_files := 0
520         var cleaned_timelines := 0
521         var cleaned_characters := 0
522         var cleaned_events := 0
523         var cleaned_glossaries := 0
524
525         var current_timeline := _close_active_timeline()
526
527         # Delete all Dialogic CSV files and their translation files.
528         for csv_path: String in DialogicResourceUtil.list_resources_of_type(".csv"):
529                 var csv_path_parts: PackedStringArray = csv_path.split("/")
530                 var csv_name: String = csv_path_parts[-1].trim_suffix(".csv")
531
532                 # Handle Dialogic CSVs only.
533                 if not csv_name.begins_with("dialogic_"):
534                         continue
535
536                 # Delete the CSV file.
537                 if OK == DirAccess.remove_absolute(csv_path):
538                         deleted_csv_files += 1
539                         print_rich("[color=green]Deleted CSV file: " + csv_path + "[/color]")
540
541                         deleted_translation_files += delete_translations_files(translation_files, csv_name)
542                 else:
543                         print_rich("[color=yellow]Failed to delete CSV file: " + csv_path + "[/color]")
544
545         # Clean timelines.
546         for timeline_path: String in DialogicResourceUtil.list_resources_of_type(".dtl"):
547
548                 # Process the timeline.
549                 var timeline: DialogicTimeline = load(timeline_path)
550                 timeline.process()
551                 cleaned_timelines += 1
552
553                 # Remove event translation IDs.
554                 for event: DialogicEvent in timeline.events:
555
556                         if event._translation_id and not event._translation_id.is_empty():
557                                 event.remove_translation_id()
558                                 event.update_text_version()
559                                 cleaned_events += 1
560
561                                 if "character" in event:
562                                         # Remove character translation IDs.
563                                         var character: DialogicCharacter = event.character
564
565                                         if character != null and not character._translation_id.is_empty():
566                                                 character.remove_translation_id()
567                                                 cleaned_characters += 1
568
569                 timeline.set_meta("timeline_not_saved", true)
570                 ResourceSaver.save(timeline, timeline_path)
571
572         _erase_glossary_translation_ids()
573         _erase_character_name_translation_ids()
574
575         ProjectSettings.set_setting('dialogic/translation/id_counter', 16)
576         ProjectSettings.set_setting('internationalization/locale/translations', PackedStringArray(translation_files))
577         ProjectSettings.save()
578
579         find_parent('EditorView').plugin_reference.get_editor_interface().get_resource_filesystem().scan_sources()
580
581         var status_message := "Timelines cleaned {cleaned_timelines}
582                 Events cleaned {cleaned_events}
583                 Characters cleaned {cleaned_characters}
584                 Glossaries cleaned {cleaned_glossaries}
585
586                 CSVs erased {erased_csv_files}
587                 Translations erased {erased_translation_files}"
588
589         var status_message_args := {
590                 'cleaned_timelines': cleaned_timelines,
591                 'cleaned_characters': cleaned_characters,
592                 'cleaned_events': cleaned_events,
593                 'cleaned_glossaries': cleaned_glossaries,
594                 'erased_csv_files': deleted_csv_files,
595                 'erased_translation_files': deleted_translation_files,
596         }
597
598         _silently_open_timeline(current_timeline)
599
600         # Trigger reimport.
601         find_parent('EditorView').plugin_reference.get_editor_interface().get_resource_filesystem().scan_sources()
602
603         # Clear the internal settings.
604         ProjectSettings.clear('dialogic/translation/intern/save_mode')
605         ProjectSettings.clear('dialogic/translation/intern/file_mode')
606         ProjectSettings.clear('dialogic/translation/intern/translation_folder')
607
608         _verify_translation_file()
609         %StatusMessage.text = status_message.format(status_message_args)
610
611
612 func _erase_glossary_translation_ids() -> void:
613         # Clean glossary.
614         var glossary_paths: Array = ProjectSettings.get_setting('dialogic/glossary/glossary_files', [])
615
616         for glossary_path: String in glossary_paths:
617                 var glossary: DialogicGlossary = load(glossary_path)
618                 glossary.remove_translation_id()
619                 glossary.remove_entry_translation_ids()
620                 glossary.clear_translation_keys()
621                 ResourceSaver.save(glossary, glossary_path)
622                 print_rich("[color=green]Cleaned up glossary file: " + glossary_path + "[/color]")
623
624
625 func _erase_character_name_translation_ids() -> void:
626         for character_path: String in DialogicResourceUtil.list_resources_of_type('.dch'):
627                 var character: DialogicCharacter = load(character_path)
628
629                 character.remove_translation_id()
630                 ResourceSaver.save(character)
631
632
633 ## Closes the current timeline in the Dialogic Editor and returns the timeline
634 ## as a resource.
635 ## If no timeline has been opened, returns null.
636 func _close_active_timeline() -> Resource:
637         var timeline_node: DialogicEditor = settings_editor.editors_manager.editors['Timeline']['node']
638         # We will close this timeline to ensure it will properly update.
639         # By saving this reference, we can open it again.
640         var current_timeline := timeline_node.current_resource
641         # Clean the current editor, this will also close the timeline.
642         settings_editor.editors_manager.clear_editor(timeline_node)
643
644         return current_timeline
645
646
647 ## Opens the timeline resource into the Dialogic Editor.
648 ## If the timeline is null, does nothing.
649 func _silently_open_timeline(timeline_to_open: Resource) -> void:
650         if timeline_to_open != null:
651                 settings_editor.editors_manager.edit_resource(timeline_to_open, true, true)
652
653
654 ## Checks [param locale] for unique locales that have not been added
655 ## to the [_unique_locales] array yet.
656 func _collect_locale(locale: String) -> void:
657         if _unique_locales.has(locale):
658                 return
659
660         _unique_locales.append(locale)