Skip to content

Commit

Permalink
Add Character Name Translation (#1898)
Browse files Browse the repository at this point in the history
* Generate translation for character names.

* Add property translation to `DialogicCharacter`.

* Fix property key.

* Improve counting CSV updates and additions.

* Improve `Update CSV files` status message.

* Add check if `current_resource` is not `null`.

* Fix file paths for characters and `Per Timeline` mode.

* Refactor CSV and translation deletion.

* Fix using wrong default name for character CSV.

* Lock Translation Settings if CSV were generated.

* Improve deletion of CSV related items.

* Hide dialogue window.

* Improve Translation Settings text.

* Add info about deleting translation IDs.

* Use translated character nicknames.

* Add missing type.

* Remove unused variable.

* Add missing newlines.

* Remove redundant `await`.

* Add `get_display_name_translated`.

* Check if event has `character`.

* Add type Godot cannot infer.

* Fix `characters` variable exist check.

* Update method usage.

* Check if translation is translation key.

* Fix check if translation is translation key.

* Fix duplicates of character properties in the CSV.
They duplicated if there were multiple timelines referencing characters.

* Fix word.

* Fix handling timeline CSV collecting.

* Small fixes for character line collection

* Fix two translation bugs

- Fixed translated nicknames method complaining about PackedStringArray (godot being stupid)
- Fixed event translations being compared to incorrect key for fallback.

* Fix closing and opening editor timeline on deletion.

---------

Co-authored-by: Jowan-Spooner <raban-loeffler@posteo.de>
  • Loading branch information
CakeVR and Jowan-Spooner authored Nov 30, 2023
1 parent a8f075b commit d280bed
Show file tree
Hide file tree
Showing 11 changed files with 612 additions and 221 deletions.
14 changes: 11 additions & 3 deletions addons/dialogic/Editor/Common/sidebar.gd
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,17 @@ func update_content_list(list:PackedStringArray) -> void:
if list.is_empty():
return

for i in editors_manager.resource_helper.timeline_directory:
if editors_manager.resource_helper.timeline_directory[i] == editors_manager.get_current_editor().current_resource.resource_path:
editors_manager.resource_helper.label_directory[i] = list
var current_resource: Resource = editors_manager.get_current_editor().current_resource

if current_resource != null:
var current_resource_path := current_resource.resource_path

for i in editors_manager.resource_helper.timeline_directory:

if editors_manager.resource_helper.timeline_directory[i] == current_resource_path:
editors_manager.resource_helper.label_directory[i] = list


editors_manager.resource_helper.label_directory[''] = list
DialogicUtil.set_editor_setting('label_ref', editors_manager.resource_helper.label_directory)

4 changes: 4 additions & 0 deletions addons/dialogic/Editor/Events/Fields/ComplexPicker.gd
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ func _exit_tree():
func take_autofocus():
%Search.grab_focus()


func set_enabled(is_enabled: bool) -> void:
%SelectButton.disabled = !is_enabled

################################################################################
## SEARCH & SUGGESTION POPUP
################################################################################
Expand Down
11 changes: 8 additions & 3 deletions addons/dialogic/Editor/Events/Fields/FilePicker.gd
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,16 @@ func set_value(value:String) -> void:
else:
%Field.custom_minimum_size.x = 0
%Field.expand_to_text_length = true

%Field.text = text

%ClearButton.visible = !value.is_empty() and !hide_reset



func set_enabled(is_enabled: bool) -> void:
%Field.editable = is_enabled
%OpenButton.disabled = !is_enabled
%ClearButton.disabled = !is_enabled


func _on_OpenButton_pressed() -> void:
Expand Down
185 changes: 125 additions & 60 deletions addons/dialogic/Editor/Settings/csv_file.gd
Original file line number Diff line number Diff line change
Expand Up @@ -22,103 +22,168 @@ var file: FileAccess
var used_file_path: String

## The amount of events that were updated in the CSV file.
var updated_events: int = 0
var updated_rows: int = 0

## The amount of events that were added to the CSV file.
var new_events: int = 0
var new_rows: int = 0


## Stores all character names from the current CSV.
##
## If this is CSV file for timeline events, every appearing speaker will be
## added once to this dictionary by their translation ID.
## If the translation ID does not exist, a new one will be generated.
##
## If this is the character name CSV file, this field captures all characters
## that were added to [member lines] using the
## [method collect_lines_from_characters].
##
## Key: String, Value: PackedStringArray
var collected_characters: Dictionary = {}

## Attempts to load the CSV file from [param file_path].
## If the file does not exist, a single entry is added to the [member lines]
## array.
func _init(file_path: String, original_locale: String) -> void:
used_file_path = file_path
used_file_path = file_path

# The first entry must be the locale row.
# [method collect_lines_from_timeline] will add the other locales, if any.
var locale_array_line := PackedStringArray(["keys", original_locale])
lines.append(locale_array_line)
# The first entry must be the locale row.
# [method collect_lines_from_timeline] will add the other locales, if any.
var locale_array_line := PackedStringArray(["keys", original_locale])
lines.append(locale_array_line)

if not FileAccess.file_exists(file_path):
is_new_file = true
if not FileAccess.file_exists(file_path):
is_new_file = true

# The "keys" and original locale are the only columns in a new file.
# For example: "keys, en"
column_count = 2
# The "keys" and original locale are the only columns in a new file.
# For example: "keys, en"
column_count = 2

file = FileAccess.open(file_path, FileAccess.WRITE)
return
file = FileAccess.open(file_path, FileAccess.WRITE)
return

file = FileAccess.open(file_path, FileAccess.READ)
file = FileAccess.open(file_path, FileAccess.READ)

var locale_csv_row := file.get_csv_line()
column_count = locale_csv_row.size()
var locale_key := locale_csv_row[0]
var locale_csv_row := file.get_csv_line()
column_count = locale_csv_row.size()
var locale_key := locale_csv_row[0]

old_lines[locale_key] = locale_csv_row
old_lines[locale_key] = locale_csv_row

_read_file_into_lines()
_read_file_into_lines()


## Private function to read the CSV file into the [member lines] array.
func _read_file_into_lines() -> void:
while not file.eof_reached():
var line := file.get_csv_line()
var row_key := line[0]
old_lines[row_key] = line
while not file.eof_reached():
var line := file.get_csv_line()
var row_key := line[0]
old_lines[row_key] = line


## Collects names from the given [param characters] and adds them to the
## [member lines].
##
## If this is the character name CSV file, use this method to
## take previously collected characters from other [class DialogicCsvFile]s.
func collect_lines_from_characters(characters: Dictionary) -> void:
for character in characters.values():

# Check if the character has a valid translation ID.
if character._translation_id == null or character._translation_id.is_empty():
character.add_translation_id()

if character._translation_id in collected_characters:
continue
else:
collected_characters[character._translation_id] = character

# Add row for display names.
var name_property := DialogicCharacter.TranslatedProperties.NAME
var display_name_key: String = character.get_property_translation_key(name_property)
var line_value: String = character.display_name
var array_line := PackedStringArray([display_name_key, line_value])
lines.append(array_line)

var character_nicknames: Array = character.nicknames
if character_nicknames.is_empty() or (character_nicknames.size() == 1 and character_nicknames[0].is_empty()):
return

# Add row for nicknames.
var nick_name_property := DialogicCharacter.TranslatedProperties.NICKNAMES
var nickname_string: String = ", ".join(character_nicknames)
var nickname_name_line_key: String = character.get_property_translation_key(nick_name_property)
var nick_array_line := PackedStringArray([nickname_name_line_key, nickname_string])
lines.append(nick_array_line)


## Collects translatable events from the given [param timeline] and adds
## them to the [member lines].
##
## If this is a timeline CSV file,
func collect_lines_from_timeline(timeline: DialogicTimeline) -> void:
for event in timeline.events:

if event.can_be_translated():

func collect_lines_from_timeline(timeline: DialogicTimeline) -> void:
for event in timeline.events:
if event._translation_id.is_empty():
event.add_translation_id()
event.update_text_version()

var properties: Array = event._get_translatable_properties()

for property in properties:
var line_key: String = event.get_property_translation_key(property)
var line_value: String = event._get_property_original_translation(property)
var array_line := PackedStringArray([line_key, line_value])
lines.append(array_line)

if event.can_be_translated():
if not "character" in event:
continue

if event._translation_id.is_empty():
event.add_translation_id()
event.update_text_version()
var character: DialogicCharacter = event.character

for property in event._get_translatable_properties():
var line_key: String = event.get_property_translation_key(property)
var line_value: String = event._get_property_original_translation(property)
var array_line := PackedStringArray([line_key, line_value])
lines.append(array_line)
if (character != null
and not collected_characters.has(character._translation_id)):
collected_characters[character._translation_id] = character


## Clears the CSV file on disk and writes the current [member lines] array to it.
## Uses the [member old_lines] dictionary to update existing translations.
## If a translation row misses a column, a trailing comma will be added to
## conform to the CSV file format.
func update_csv_file_on_disk() -> void:
# Clear the current CSV file.
file = FileAccess.open(used_file_path, FileAccess.WRITE)
# Clear the current CSV file.
file = FileAccess.open(used_file_path, FileAccess.WRITE)

for line in lines:
var row_key := line[0]
for line in lines:
var row_key := line[0]

# In case there might be translations for this line already,
# add them at the end again (orig locale text is replaced).
if row_key in old_lines:
var old_line: PackedStringArray = old_lines[row_key]
var updated_line: PackedStringArray = line + old_line.slice(2)
# In case there might be translations for this line already,
# add them at the end again (orig locale text is replaced).
if row_key in old_lines:
var old_line: PackedStringArray = old_lines[row_key]
var updated_line: PackedStringArray = line + old_line.slice(2)

var line_columns: int = updated_line.size()
var line_columns_to_add := column_count - line_columns
var line_columns: int = updated_line.size()
var line_columns_to_add := column_count - line_columns

# Add trailing commas to match the amount of columns.
for _i in range(line_columns_to_add):
updated_line.append("")
# Add trailing commas to match the amount of columns.
for _i in range(line_columns_to_add):
updated_line.append("")

file.store_csv_line(updated_line)
updated_events += 1
file.store_csv_line(updated_line)
updated_rows += 1

else:
var line_columns: int = line.size()
var line_columns_to_add := column_count - line_columns
else:
var line_columns: int = line.size()
var line_columns_to_add := column_count - line_columns

# Add trailing commas to match the amount of columns.
for _i in range(line_columns_to_add):
line.append("")
# Add trailing commas to match the amount of columns.
for _i in range(line_columns_to_add):
line.append("")

file.store_csv_line(line)
new_events += 1
file.store_csv_line(line)
new_rows += 1

file.close()
file.close()
22 changes: 11 additions & 11 deletions addons/dialogic/Editor/Settings/settings_general.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_kqhx5"]
[ext_resource type="PackedScene" uid="uid://7mvxuaulctcq" path="res://addons/dialogic/Editor/Events/Fields/FilePicker.tscn" id="3_i7rug"]

[sub_resource type="Image" id="Image_pqmjp"]
[sub_resource type="Image" id="Image_e2a0f"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
Expand All @@ -13,8 +13,8 @@ data = {
"width": 16
}

[sub_resource type="ImageTexture" id="ImageTexture_fyskv"]
image = SubResource("Image_pqmjp")
[sub_resource type="ImageTexture" id="ImageTexture_wyypx"]
image = SubResource("Image_e2a0f")

[node name="General" type="VBoxContainer"]
anchors_preset = 15
Expand All @@ -35,15 +35,15 @@ text = "Color Palette"
[node name="HintTooltip" parent="PaletteTitle" instance=ExtResource("2_kqhx5")]
layout_mode = 2
tooltip_text = "These colors are used for the events."
texture = SubResource("ImageTexture_fyskv")
texture = SubResource("ImageTexture_wyypx")
hint_text = "These colors are used for the events."

[node name="ResetColorsButton" type="Button" parent="PaletteTitle"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 0
tooltip_text = "Reset Colors to default"
icon = SubResource("ImageTexture_fyskv")
icon = SubResource("ImageTexture_wyypx")
flat = true

[node name="ScrollContainer" type="ScrollContainer" parent="."]
Expand Down Expand Up @@ -72,7 +72,7 @@ tooltip_text = "The layout scene configured in the Layout editor is automaticall
instanced when calling Dialogic.start(). Depending on your game,
you might want it to be deleted after the dialogue, be hidden
(as reinstancing often is wasting resources) or kept visible. "
texture = SubResource("ImageTexture_fyskv")
texture = SubResource("ImageTexture_wyypx")
hint_text = "The layout scene configured in the Layout editor is automatically
instanced when calling Dialogic.start(). Depending on your game,
you might want it to be deleted after the dialogue, be hidden
Expand Down Expand Up @@ -121,7 +121,7 @@ layout_mode = 2
tooltip_text = "Configure where dialogic looks for custom modules.
You will have to restart the project to see the change take action."
texture = SubResource("ImageTexture_fyskv")
texture = SubResource("ImageTexture_wyypx")
hint_text = "Configure where dialogic looks for custom modules.
You will have to restart the project to see the change take action."
Expand All @@ -139,7 +139,7 @@ layout_mode = 2
size_flags_horizontal = 3
placeholder = "res://addons/dialogic_additions/Events"
file_mode = 2
resource_icon = SubResource("ImageTexture_fyskv")
resource_icon = SubResource("ImageTexture_wyypx")

[node name="VSeparator" type="VSeparator" parent="HBoxContainer6"]
layout_mode = 2
Expand All @@ -164,7 +164,7 @@ text = "Extension Creator "
[node name="HintTooltip" parent="HBoxContainer6/ExtensionsPanel/VBox/HBoxContainer6" instance=ExtResource("2_kqhx5")]
layout_mode = 2
tooltip_text = "Use the Exension Creator to quickly setup custom modules!"
texture = SubResource("ImageTexture_fyskv")
texture = SubResource("ImageTexture_wyypx")
hint_text = "Use the Exension Creator to quickly setup custom modules!"

[node name="CreateExtensionButton" type="Button" parent="HBoxContainer6/ExtensionsPanel/VBox"]
Expand Down Expand Up @@ -228,7 +228,7 @@ text = "Timer processing"
[node name="HintTooltip" parent="HBoxContainer7" instance=ExtResource("2_kqhx5")]
layout_mode = 2
tooltip_text = "Change whether dialogics timers process in physics_process (frame-rate independent) or process."
texture = SubResource("ImageTexture_fyskv")
texture = SubResource("ImageTexture_wyypx")
hint_text = "Change whether dialogics timers process in physics_process (frame-rate independent) or process."

[node name="HBoxContainer4" type="HBoxContainer" parent="."]
Expand Down Expand Up @@ -256,7 +256,7 @@ text = "Section Order"
[node name="HintTooltip" parent="HBoxContainer" instance=ExtResource("2_kqhx5")]
layout_mode = 2
tooltip_text = "You can change the order of the event sections here. "
texture = SubResource("ImageTexture_fyskv")
texture = SubResource("ImageTexture_wyypx")
hint_text = "You can change the order of the event sections here. "

[node name="SectionList" type="Tree" parent="."]
Expand Down
Loading

0 comments on commit d280bed

Please sign in to comment.