You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

341 lines
10 KiB

6 months ago
@tool
## Resource used to store glossary entries. Can be saved to disc and used as a glossary.
## Add/create glossaries fom the glossaries editor
class_name DialogicGlossary
extends Resource
## Stores all entries for the glossary.
##
## The value may either be a dictionary, representing an entry, or
## a string, representing the actual key for the key used.
## The string key-value pairs are the alias keys, they allow to redirect
## the actual glossary entry.
@export var entries: Dictionary = {}
## If false, no entries from this glossary will be shown
@export var enabled: bool = true
## Refers to the translation type of this resource used for CSV translation files.
const RESOURCE_NAME := "Glossary"
## The name of glossary entries, the value is the key in [member entries].
## This constant is used for CSV translation files.
const NAME_PROPERTY := "name"
## Property in a glossary entry. Alternative words for the entry name.
const ALTERNATIVE_PROPERTY := "alternatives"
## Property in a glossary entry.
const TITLE_PROPERTY := "title"
## Property in a glossary entry.
const TEXT_PROPERTY := "text"
## Property in a glossary entry.
const EXTRA_PROPERTY := "extra"
## Property in a glossary entry. The translation ID of the entry.
## May be empty if the entry has not been translated yet.
const TRANSLATION_PROPERTY := "_translation_id"
## Property in a glossary entry.
const REGEX_OPTION_PROPERTY := "regex_options"
## Prefix used for private properties in entries.
## Ignored when entries are translated.
const PRIVATE_PROPERTY_PREFIX := "_"
## Private ID assigned when this glossary is translated.
@export var _translation_id: String = ""
## Private lookup table used to find the translation ID of a glossary entry.
## The keys (String) are all translated words that may trigger a glossary entry to
## be shown.
## The values (String) are the translation ID.
@export var _translation_keys: Dictionary = {}
## Removes an entry and all its aliases (alternative property) from
## the glossary.
## [param entry_key] may be an entry name or an alias.
##
## Returns true if the entry matching the given [param entry_key] was found.
func remove_entry(entry_key: String) -> bool:
var entry: Dictionary = get_entry(entry_key)
if entry.is_empty():
return false
var aliases: Array = entry.get(ALTERNATIVE_PROPERTY, [])
for alias: String in aliases:
_remove_entry_alias(alias)
entries.erase(entry_key)
return true
## This is an internal method.
## Erases an entry alias key based the given [param entry_key].
##
## Returns true if [param entry_key] lead to a value and the value
## was an alias.
##
## This method does not update the entry's alternative property.
func _remove_entry_alias(entry_key: String) -> bool:
var value: Variant = entries.get(entry_key, null)
if value == null or value is Dictionary:
return false
entries.erase(entry_key)
return true
## Updates the glossary entry's name and related alias keys.
## The [param old_entry_key] is the old unique name of the entry.
## The [param new_entry_key] is the new unique name of the entry.
##
## This method fails if the [param old_entry_key] does not exist.
## Do not use this to update alternative names.
## In order to update alternative names, delete all with
## [method _remove_entry_alias] and then add them again with
## [method _add_entry_key_alias].
func replace_entry_key(old_entry_key: String, new_entry_key: String) -> void:
var entry := get_entry(old_entry_key)
if entry == null:
return
entry.name = new_entry_key
entries.erase(old_entry_key)
entries[new_entry_key] = entry
## Gets the glossary entry for the given [param entry_key].
## If there is no matching entry, an empty Dictionary will be returned.
## Valid glossary entry dictionaries will never be empty.
func get_entry(entry_key: String) -> Dictionary:
var entry: Variant = entries.get(entry_key, {})
# Handle alias value.
if entry is String:
entry = entries.get(entry, {})
return entry
## This is an internal method.
## The [param entry_key] must be valid entry key for an entry.
## Adds the [param alias] as a valid entry key for that entry.
##
## Returns the index of the entry, -1 if the entry does not exist.
func _add_entry_key_alias(entry_key: String, alias: String) -> bool:
var entry := get_entry(entry_key)
var alias_entry := get_entry(alias)
if not entry.is_empty() and alias_entry.is_empty():
entries[alias] = entry_key
return true
return false
## Adds [param entry] to the glossary if it does not exist.
## If it does exist, returns false.
func try_add_entry(entry: Dictionary) -> bool:
var entry_key: String = entry[NAME_PROPERTY]
if entries.has(entry_key):
return false
entries[entry_key] = entry
for alternative: String in entry.get(ALTERNATIVE_PROPERTY, []):
entries[alternative.strip_edges()] = entry_key
return true
## Returns an array of words that can trigger the glossary popup.
## This method respects whether translation is enabled or not.
## The words may be: The entry key and the alternative words.
func _get_word_options(entry_key: String) -> Array:
var word_options: Array = []
var translation_enabled: bool = ProjectSettings.get_setting("dialogic/translation/enabled", false)
if not translation_enabled:
word_options.append(entry_key)
for alternative: String in get_entry(entry_key).get(ALTERNATIVE_PROPERTY, []):
word_options.append(alternative.strip_edges())
return word_options
var translation_entry_key_id: String = get_property_translation_key(entry_key, NAME_PROPERTY)
if translation_entry_key_id.is_empty():
return []
var translated_entry_key := tr(translation_entry_key_id)
if not translated_entry_key == translation_entry_key_id:
word_options.append(translated_entry_key)
var translation_alternatives_id: String = get_property_translation_key(entry_key, ALTERNATIVE_PROPERTY)
var translated_alternatives_str := tr(translation_alternatives_id)
if not translated_alternatives_str == translation_alternatives_id:
var translated_alternatives := translated_alternatives_str.split(",")
for alternative: String in translated_alternatives:
word_options.append(alternative.strip_edges())
return word_options
## Gets the regex option for the given [param entry_key].
## If the regex option does not exist, it will be generated.
##
## A regex option is the accumulation of valid words that can trigger the
## glossary popup.
##
## The [param entry_key] must be valid or an error will occur.
func get_set_regex_option(entry_key: String) -> String:
var entry: Dictionary = get_entry(entry_key)
var regex_options: Dictionary = entry.get(REGEX_OPTION_PROPERTY, {})
if regex_options.is_empty():
entry[REGEX_OPTION_PROPERTY] = regex_options
var locale_key: String = TranslationServer.get_locale()
var regex_option: String = regex_options.get(locale_key, "")
if not regex_option.is_empty():
return regex_option
var word_options: Array = _get_word_options(entry_key)
regex_option = "|".join(word_options)
regex_options[locale_key] = regex_option
return regex_option
#region ADD AND CLEAR TRANSLATION KEYS
## This is automatically called, no need to use this.
func add_translation_id() -> String:
_translation_id = DialogicUtil.get_next_translation_id()
return _translation_id
## Removes the translation ID of this glossary.
func remove_translation_id() -> void:
_translation_id = ""
## Removes the translation ID of all glossary entries.
func remove_entry_translation_ids() -> void:
for entry: Variant in entries.values():
# Ignore aliases.
if entry is String:
continue
if entry.has(TRANSLATION_PROPERTY):
entry[TRANSLATION_PROPERTY] = ""
## Clears the lookup tables using translation keys.
func clear_translation_keys() -> void:
const RESOURCE_NAME_KEY := RESOURCE_NAME + "/"
for translation_key: String in entries.keys():
if translation_key.begins_with(RESOURCE_NAME_KEY):
entries.erase(translation_key)
_translation_keys.clear()
#endregion
#region GET AND SET TRANSLATION IDS AND KEYS
## Returns a key used to reference this glossary in the translation CSV file.
##
## Time complexity: O(1)
func get_property_translation_key(entry_key: String, property: String) -> String:
var entry := get_entry(entry_key)
if entry == null:
return ""
var entry_translation_key: String = entry.get(TRANSLATION_PROPERTY, "")
if entry_translation_key.is_empty() or _translation_id.is_empty():
return ""
var glossary_csv_key := (RESOURCE_NAME
.path_join(_translation_id)
.path_join(entry_translation_key)
.path_join(property))
return glossary_csv_key
## Returns the translation key prefix for this glossary.
## The resulting format will look like this: Glossary/a2/
## This prefix can be used to find translations for this glossary.
func _get_glossary_translation_id_prefix() -> String:
return (
DialogicGlossary.RESOURCE_NAME
.path_join(_translation_id)
)
## Returns the translation key for the given [param glossary_translation_id] and
## [param entry_translation_id].
##
## By key, we refer to the uniquely named property per translation entry.
##
## The resulting format will look like this: Glossary/a2/b4/name
func _get_glossary_translation_key(entry_translation_id: String, property: String) -> String:
return (
DialogicGlossary.RESOURCE_NAME
.path_join(_translation_id)
.path_join(entry_translation_id)
.path_join(property)
)
## Tries to get the glossary entry's translation ID.
## If it does not exist, a new one will be generated.
func get_set_glossary_entry_translation_id(entry_key: String) -> String:
var glossary_entry: Dictionary = get_entry(entry_key)
var entry_translation_id := ""
var glossary_translation_id: String = glossary_entry.get(TRANSLATION_PROPERTY, "")
if glossary_translation_id.is_empty():
entry_translation_id = DialogicUtil.get_next_translation_id()
glossary_entry[TRANSLATION_PROPERTY] = entry_translation_id
else:
entry_translation_id = glossary_entry[TRANSLATION_PROPERTY]
return entry_translation_id
## Tries to get the glossary's translation ID.
## If it does not exist, a new one will be generated.
func get_set_glossary_translation_id() -> String:
if _translation_id == null or _translation_id.is_empty():
add_translation_id()
return _translation_id
#endregion