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
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
|