@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