289 lines
10 KiB
GDScript
289 lines
10 KiB
GDScript
## A collection of utility functions for working with dialogue translations.
|
|
class_name DMTranslationUtilities extends RefCounted
|
|
|
|
|
|
## Generate translation keys from some text.
|
|
static func generate_static_line_ids_for_project() -> void:
|
|
var rng: RandomNumberGenerator = RandomNumberGenerator.new()
|
|
rng.randomize()
|
|
|
|
for file_path: String in DMCache.get_files():
|
|
var text: String = FileAccess.get_file_as_string(file_path)
|
|
|
|
text = generate_static_line_ids_for_text(text, file_path)
|
|
|
|
var file: FileAccess = FileAccess.open(file_path, FileAccess.WRITE)
|
|
file.store_string(text)
|
|
file.close()
|
|
|
|
|
|
## Generate static line IDs for some text.
|
|
static func generate_static_line_ids_for_text(text: String, file_path: String) -> String:
|
|
var lines: PackedStringArray = text.split("\n")
|
|
var compiled_lines: Dictionary = DMCompiler.compile_string(text, "").lines
|
|
|
|
# Add in any that are missing
|
|
for i: int in lines.size():
|
|
var line: String = lines[i]
|
|
var l: String = line.strip_edges()
|
|
|
|
if not [DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE].has(DMCompiler.get_line_type(l)): continue
|
|
if not compiled_lines.has(str(i)): continue
|
|
|
|
if "[ID:" in line: continue
|
|
|
|
var translatable_text: String = ""
|
|
if l.begins_with("- "):
|
|
translatable_text = DMCompiler.extract_translatable_string(l)
|
|
else:
|
|
translatable_text = l.substr(l.find(":") + 1)
|
|
|
|
var key: String = _generate_id(file_path)
|
|
while key in DMCache.known_static_ids:
|
|
key = _generate_id(file_path)
|
|
line = line.replace("\\n", "!NEWLINE!")
|
|
translatable_text = translatable_text.replace("\\n", "!NEWLINE!")
|
|
lines[i] = line.replace(translatable_text, translatable_text + " [ID:%s]" % [key]).replace("!NEWLINE!", "\\n")
|
|
|
|
DMCache.known_static_ids[key] = file_path
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
## Get a random-ish ID for a line.
|
|
static func _generate_id(file_path: String) -> String:
|
|
return (file_path.sha1_text().substr(0, 6) + "_" + str(randi() % 1000000).sha1_text().substr(0, 6)).to_upper()
|
|
|
|
|
|
## Export dialogue and responses to CSV.
|
|
static func export_translations_to_csv(to_path: String, text: String, dialogue_path: String) -> void:
|
|
var default_locale: String = DMSettings.get_setting(DMSettings.DEFAULT_CSV_LOCALE, "en")
|
|
|
|
var file: FileAccess
|
|
|
|
# If the file exists, open it first and work out which keys are already in it
|
|
var existing_csv: Dictionary = {}
|
|
var delimiter: String = get_delimiter_for_csv(to_path)
|
|
var column_count: int = 2
|
|
var default_locale_column: int = 1
|
|
var character_column: int = -1
|
|
var notes_column: int = -1
|
|
if FileAccess.file_exists(to_path):
|
|
file = FileAccess.open(to_path, FileAccess.READ)
|
|
var is_first_line = true
|
|
var line: Array
|
|
while !file.eof_reached():
|
|
line = file.get_csv_line(delimiter)
|
|
if is_first_line:
|
|
is_first_line = false
|
|
column_count = line.size()
|
|
for i in range(1, line.size()):
|
|
if line[i] == default_locale:
|
|
default_locale_column = i
|
|
elif line[i] == "_character":
|
|
character_column = i
|
|
elif line[i] == "_notes":
|
|
notes_column = i
|
|
|
|
# Make sure the line isn't empty before adding it
|
|
if line.size() > 0 and line[0].strip_edges() != "":
|
|
existing_csv[line[0]] = line
|
|
|
|
# The character column wasn't found in the existing file but the setting is turned on
|
|
if character_column == -1 and DMSettings.get_setting(DMSettings.INCLUDE_CHARACTER_IN_TRANSLATION_EXPORTS, false):
|
|
character_column = column_count
|
|
column_count += 1
|
|
existing_csv["keys"].append("_character")
|
|
|
|
# The notes column wasn't found in the existing file but the setting is turned on
|
|
if notes_column == -1 and DMSettings.get_setting(DMSettings.INCLUDE_NOTES_IN_TRANSLATION_EXPORTS, false):
|
|
notes_column = column_count
|
|
column_count += 1
|
|
existing_csv["keys"].append("_notes")
|
|
|
|
# Start a new file
|
|
file = FileAccess.open(to_path, FileAccess.WRITE)
|
|
|
|
if not FileAccess.file_exists(to_path):
|
|
var headings: PackedStringArray = ["keys", default_locale] + DMSettings.get_setting(DMSettings.EXTRA_CSV_LOCALES, [])
|
|
if DMSettings.get_setting(DMSettings.INCLUDE_CHARACTER_IN_TRANSLATION_EXPORTS, false):
|
|
character_column = headings.size()
|
|
headings.append("_character")
|
|
if DMSettings.get_setting(DMSettings.INCLUDE_NOTES_IN_TRANSLATION_EXPORTS, false):
|
|
notes_column = headings.size()
|
|
headings.append("_notes")
|
|
|
|
file.store_csv_line(headings, delimiter)
|
|
column_count = headings.size()
|
|
|
|
# Write our translations to file
|
|
var known_keys: PackedStringArray = []
|
|
|
|
var dialogue = DMCompiler.compile_string(text, dialogue_path).lines
|
|
|
|
# Make a list of stuff that needs to go into the file
|
|
var lines_to_save = []
|
|
for key in dialogue.keys():
|
|
var line: Dictionary = dialogue.get(key)
|
|
|
|
if not line.type in [DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE]: continue
|
|
|
|
var translation_key: String = line.get(&"translation_key", line.text)
|
|
|
|
if translation_key in known_keys: continue
|
|
|
|
known_keys.append(translation_key)
|
|
|
|
var line_to_save: PackedStringArray = []
|
|
if existing_csv.has(translation_key):
|
|
line_to_save = existing_csv.get(translation_key)
|
|
line_to_save.resize(column_count)
|
|
existing_csv.erase(translation_key)
|
|
else:
|
|
line_to_save.resize(column_count)
|
|
line_to_save[0] = translation_key
|
|
|
|
line_to_save[default_locale_column] = line.text
|
|
if character_column > -1:
|
|
line_to_save[character_column] = "(response)" if line.type == DMConstants.TYPE_RESPONSE else line.character
|
|
if notes_column > -1:
|
|
line_to_save[notes_column] = line.get("notes", "")
|
|
|
|
lines_to_save.append(line_to_save)
|
|
|
|
# Store lines in the file, starting with anything that already exists that hasn't been touched
|
|
for line in existing_csv.values():
|
|
file.store_csv_line(line, delimiter)
|
|
for line in lines_to_save:
|
|
file.store_csv_line(line, delimiter)
|
|
|
|
file.close()
|
|
|
|
|
|
## Get the delimier used for an existing CSV
|
|
static func get_delimiter_for_csv(path: String) -> String:
|
|
if FileAccess.file_exists(path):
|
|
var import_path: String = "%s.%s" % [path, "import"]
|
|
var import_file: ConfigFile = ConfigFile.new()
|
|
if import_file.load(import_path) == OK:
|
|
match import_file.get_value("params", "delimier", 0):
|
|
0:
|
|
return ","
|
|
1:
|
|
return ";"
|
|
2:
|
|
return "\t"
|
|
|
|
match DMSettings.get_setting(DMSettings.DEFAULT_CSV_DELIMITER, "Comma"):
|
|
"Comma":
|
|
return ","
|
|
"Semicolon":
|
|
return ";"
|
|
"Tab":
|
|
return "\t"
|
|
|
|
return ","
|
|
|
|
|
|
## Save any character names in a file to CSV.
|
|
static func export_character_names_to_csv(to_path: String, text: String, dialogue_path: String) -> void:
|
|
var file: FileAccess
|
|
|
|
# If the file exists, open it first and work out which keys are already in it
|
|
var existing_csv = {}
|
|
var delimiter: String = get_delimiter_for_csv(to_path)
|
|
var commas = []
|
|
if FileAccess.file_exists(to_path):
|
|
file = FileAccess.open(to_path, FileAccess.READ)
|
|
var is_first_line = true
|
|
var line: Array
|
|
while !file.eof_reached():
|
|
line = file.get_csv_line(delimiter)
|
|
if is_first_line:
|
|
is_first_line = false
|
|
for i in range(2, line.size()):
|
|
commas.append("")
|
|
# Make sure the line isn't empty before adding it
|
|
if line.size() > 0 and line[0].strip_edges() != "":
|
|
existing_csv[line[0]] = line
|
|
|
|
# Start a new file
|
|
file = FileAccess.open(to_path, FileAccess.WRITE)
|
|
|
|
if not file.file_exists(to_path):
|
|
file.store_csv_line(["keys", DMSettings.get_setting(DMSettings.DEFAULT_CSV_LOCALE, "en")], delimiter)
|
|
|
|
# Write our translations to file
|
|
var known_keys: PackedStringArray = []
|
|
|
|
var character_names: PackedStringArray = DMCompiler.compile_string(text, dialogue_path).character_names
|
|
|
|
# Make a list of stuff that needs to go into the file
|
|
var lines_to_save = []
|
|
for character_name in character_names:
|
|
if character_name in known_keys: continue
|
|
|
|
known_keys.append(character_name)
|
|
|
|
if existing_csv.has(character_name):
|
|
var existing_line = existing_csv.get(character_name)
|
|
existing_line[1] = character_name
|
|
lines_to_save.append(existing_line)
|
|
existing_csv.erase(character_name)
|
|
else:
|
|
lines_to_save.append(PackedStringArray([character_name, character_name] + commas))
|
|
|
|
# Store lines in the file, starting with anything that already exists that hasn't been touched
|
|
for line in existing_csv.values():
|
|
file.store_csv_line(line, delimiter)
|
|
for line in lines_to_save:
|
|
file.store_csv_line(line, delimiter)
|
|
|
|
file.close()
|
|
|
|
|
|
## Replace translatable lines in some text using an existing CSV.
|
|
static func import_translations_from_csv(from_path: String, text: String) -> String:
|
|
if not FileAccess.file_exists(from_path): return text
|
|
|
|
# Open the CSV file and build a dictionary of the known keys
|
|
var delimiter: String = get_delimiter_for_csv(from_path)
|
|
var keys: Dictionary = {}
|
|
var file: FileAccess = FileAccess.open(from_path, FileAccess.READ)
|
|
var csv_line: Array
|
|
while !file.eof_reached():
|
|
csv_line = file.get_csv_line(delimiter)
|
|
if csv_line.size() > 1:
|
|
keys[csv_line[0]] = csv_line[1]
|
|
|
|
# Now look over each line in the dialogue and replace the content for matched keys
|
|
var lines: PackedStringArray = text.split("\n")
|
|
var start_index: int = 0
|
|
var end_index: int = 0
|
|
for i in range(0, lines.size()):
|
|
var line: String = lines[i]
|
|
var translation_key: String = DMCompiler.get_static_line_id(line)
|
|
if keys.has(translation_key):
|
|
if DMCompiler.get_line_type(line) == DMConstants.TYPE_DIALOGUE:
|
|
start_index = 0
|
|
# See if we need to skip over a character name
|
|
line = line.replace("\\:", "!ESCAPED_COLON!")
|
|
if ": " in line:
|
|
start_index = line.find(": ") + 2
|
|
lines[i] = (line.substr(0, start_index) + keys.get(translation_key) + " [ID:" + translation_key + "]").replace("!ESCAPED_COLON!", ":")
|
|
|
|
elif DMCompiler.get_line_type(line) == DMConstants.TYPE_RESPONSE:
|
|
start_index = line.find("- ") + 2
|
|
# See if we need to skip over a character name
|
|
line = line.replace("\\:", "!ESCAPED_COLON!")
|
|
if ": " in line:
|
|
start_index = line.find(": ") + 2
|
|
end_index = line.length()
|
|
if " =>" in line:
|
|
end_index = line.find(" =>")
|
|
if " [if " in line:
|
|
end_index = line.find(" [if ")
|
|
lines[i] = (line.substr(0, start_index) + keys.get(translation_key) + " [ID:" + translation_key + "]" + line.substr(end_index)).replace("!ESCAPED_COLON!", ":")
|
|
|
|
return "\n".join(lines)
|