2 ## Resource used to store glossary entries. Can be saved to disc and used as a glossary.
3 ## Add/create glossaries fom the glossaries editor
4 class_name DialogicGlossary
7 ## Stores all entries for the glossary.
9 ## The value may either be a dictionary, representing an entry, or
10 ## a string, representing the actual key for the key used.
11 ## The string key-value pairs are the alias keys, they allow to redirect
12 ## the actual glossary entry.
13 @export var entries := {}
15 ## If false, no entries from this glossary will be shown
16 @export var enabled: bool = true
18 ## Refers to the translation type of this resource used for CSV translation files.
19 const RESOURCE_NAME := "Glossary"
20 ## The name of glossary entries, the value is the key in [member entries].
21 ## This constant is used for CSV translation files.
22 const NAME_PROPERTY := "name"
23 ## Property in a glossary entry. Alternative words for the entry name.
24 const ALTERNATIVE_PROPERTY := "alternatives"
25 ## Property in a glossary entry.
26 const TITLE_PROPERTY := "title"
27 ## Property in a glossary entry.
28 const TEXT_PROPERTY := "text"
29 ## Property in a glossary entry.
30 const EXTRA_PROPERTY := "extra"
31 ## Property in a glossary entry. The translation ID of the entry.
32 ## May be empty if the entry has not been translated yet.
33 const TRANSLATION_PROPERTY := "_translation_id"
34 ## Property in a glossary entry.
35 const REGEX_OPTION_PROPERTY := "regex_options"
36 ## Prefix used for private properties in entries.
37 ## Ignored when entries are translated.
38 const PRIVATE_PROPERTY_PREFIX := "_"
41 ## Private ID assigned when this glossary is translated.
42 @export var _translation_id := ""
44 ## Private lookup table used to find the translation ID of a glossary entry.
45 ## The keys (String) are all translated words that may trigger a glossary entry to
47 ## The values (String) are the translation ID.
48 @export var _translation_keys := {}
52 ## Removes an entry and all its aliases (alternative property) from
54 ## [param entry_key] may be an entry name or an alias.
56 ## Returns true if the entry matching the given [param entry_key] was found.
57 func remove_entry(entry_key: String) -> bool:
58 var entry: Dictionary = get_entry(entry_key)
63 var aliases: Array = entry.get(ALTERNATIVE_PROPERTY, [])
65 for alias: String in aliases:
66 _remove_entry_alias(alias)
68 entries.erase(entry_key)
73 ## This is an internal method.
74 ## Erases an entry alias key based the given [param entry_key].
76 ## Returns true if [param entry_key] lead to a value and the value
79 ## This method does not update the entry's alternative property.
80 func _remove_entry_alias(entry_key: String) -> bool:
81 var value: Variant = entries.get(entry_key, null)
83 if value == null or value is Dictionary:
86 entries.erase(entry_key)
91 ## Updates the glossary entry's name and related alias keys.
92 ## The [param old_entry_key] is the old unique name of the entry.
93 ## The [param new_entry_key] is the new unique name of the entry.
95 ## This method fails if the [param old_entry_key] does not exist.
97 ## Do not use this to update alternative names.
98 ## In order to update alternative names, delete all with
99 ## [method _remove_entry_alias] and then add them again with
100 ## [method _add_entry_key_alias].
101 func replace_entry_key(old_entry_key: String, new_entry_key: String) -> void:
102 var entry := get_entry(old_entry_key)
107 entry.name = new_entry_key
109 entries.erase(old_entry_key)
110 entries[new_entry_key] = entry
113 ## Gets the glossary entry for the given [param entry_key].
114 ## If there is no matching entry, an empty Dictionary will be returned.
115 ## Valid glossary entry dictionaries will never be empty.
116 func get_entry(entry_key: String) -> Dictionary:
117 var entry: Variant = entries.get(entry_key, {})
119 # Handle alias value.
121 entry = entries.get(entry, {})
126 ## This is an internal method.
127 ## The [param entry_key] must be valid entry key for an entry.
128 ## Adds the [param alias] as a valid entry key for that entry.
130 ## Returns the index of the entry, -1 if the entry does not exist.
131 func _add_entry_key_alias(entry_key: String, alias: String) -> bool:
132 var entry := get_entry(entry_key)
133 var alias_entry := get_entry(alias)
135 if not entry.is_empty() and alias_entry.is_empty():
136 entries[alias] = entry_key
142 ## Adds [param entry] to the glossary if it does not exist.
143 ## If it does exist, returns false.
144 func try_add_entry(entry: Dictionary) -> bool:
145 var entry_key: String = entry[NAME_PROPERTY]
147 if entries.has(entry_key):
150 entries[entry_key] = entry
152 for alternative: String in entry.get(ALTERNATIVE_PROPERTY, []):
153 entries[alternative.strip_edges()] = entry_key
158 ## Returns an array of words that can trigger the glossary popup.
159 ## This method respects whether translation is enabled or not.
160 ## The words may be: The entry key and the alternative words.
161 func _get_word_options(entry_key: String) -> Array:
162 var word_options: Array = []
164 var translation_enabled: bool = ProjectSettings.get_setting("dialogic/translation/enabled", false)
166 if not translation_enabled:
167 word_options.append(entry_key)
169 for alternative: String in get_entry(entry_key).get(ALTERNATIVE_PROPERTY, []):
170 word_options.append(alternative.strip_edges())
174 var translation_entry_key_id: String = get_property_translation_key(entry_key, NAME_PROPERTY)
176 if translation_entry_key_id.is_empty():
179 var translated_entry_key := tr(translation_entry_key_id)
181 if not translated_entry_key == translation_entry_key_id:
182 word_options.append(translated_entry_key)
184 var translation_alternatives_id: String = get_property_translation_key(entry_key, ALTERNATIVE_PROPERTY)
185 var translated_alternatives_str := tr(translation_alternatives_id)
187 if not translated_alternatives_str == translation_alternatives_id:
188 var translated_alternatives := translated_alternatives_str.split(",")
190 for alternative: String in translated_alternatives:
191 word_options.append(alternative.strip_edges())
196 ## Gets the regex option for the given [param entry_key].
197 ## If the regex option does not exist, it will be generated.
199 ## A regex option is the accumulation of valid words that can trigger the
202 ## The [param entry_key] must be valid or an error will occur.
203 func get_set_regex_option(entry_key: String) -> String:
204 var entry: Dictionary = get_entry(entry_key)
206 var regex_options: Dictionary = entry.get(REGEX_OPTION_PROPERTY, {})
208 if regex_options.is_empty():
209 entry[REGEX_OPTION_PROPERTY] = regex_options
211 var locale_key: String = TranslationServer.get_locale()
212 var regex_option: String = regex_options.get(locale_key, "")
214 if not regex_option.is_empty():
217 var word_options: Array = _get_word_options(entry_key)
218 regex_option = "|".join(word_options)
220 regex_options[locale_key] = regex_option
225 #region ADD AND CLEAR TRANSLATION KEYS
227 ## This is automatically called, no need to use this.
228 func add_translation_id() -> String:
229 _translation_id = DialogicUtil.get_next_translation_id()
230 return _translation_id
233 ## Removes the translation ID of this glossary.
234 func remove_translation_id() -> void:
238 ## Removes the translation ID of all glossary entries.
239 func remove_entry_translation_ids() -> void:
240 for entry: Variant in entries.values():
246 if entry.has(TRANSLATION_PROPERTY):
247 entry[TRANSLATION_PROPERTY] = ""
250 ## Clears the lookup tables using translation keys.
251 func clear_translation_keys() -> void:
252 const RESOURCE_NAME_KEY := RESOURCE_NAME + "/"
254 for translation_key: String in entries.keys():
256 if translation_key.begins_with(RESOURCE_NAME_KEY):
257 entries.erase(translation_key)
259 _translation_keys.clear()
264 #region GET AND SET TRANSLATION IDS AND KEYS
266 ## Returns a key used to reference this glossary in the translation CSV file.
268 ## Time complexity: O(1)
269 func get_property_translation_key(entry_key: String, property: String) -> String:
270 var entry := get_entry(entry_key)
275 var entry_translation_key: String = entry.get(TRANSLATION_PROPERTY, "")
277 if entry_translation_key.is_empty() or _translation_id.is_empty():
280 var glossary_csv_key := (RESOURCE_NAME
281 .path_join(_translation_id)
282 .path_join(entry_translation_key)
283 .path_join(property))
285 return glossary_csv_key
289 ## Returns the translation key prefix for this glossary.
290 ## The resulting format will look like this: Glossary/a2/
291 ## This prefix can be used to find translations for this glossary.
292 func _get_glossary_translation_id_prefix() -> String:
294 DialogicGlossary.RESOURCE_NAME
295 .path_join(_translation_id)
299 ## Returns the translation key for the given [param glossary_translation_id] and
300 ## [param entry_translation_id].
302 ## By key, we refer to the uniquely named property per translation entry.
304 ## The resulting format will look like this: Glossary/a2/b4/name
305 func _get_glossary_translation_key(entry_translation_id: String, property: String) -> String:
307 DialogicGlossary.RESOURCE_NAME
308 .path_join(_translation_id)
309 .path_join(entry_translation_id)
314 ## Tries to get the glossary entry's translation ID.
315 ## If it does not exist, a new one will be generated.
316 func get_set_glossary_entry_translation_id(entry_key: String) -> String:
317 var glossary_entry: Dictionary = get_entry(entry_key)
318 var entry_translation_id := ""
320 var glossary_translation_id: String = glossary_entry.get(TRANSLATION_PROPERTY, "")
322 if glossary_translation_id.is_empty():
323 entry_translation_id = DialogicUtil.get_next_translation_id()
324 glossary_entry[TRANSLATION_PROPERTY] = entry_translation_id
327 entry_translation_id = glossary_entry[TRANSLATION_PROPERTY]
329 return entry_translation_id
332 ## Tries to get the glossary's translation ID.
333 ## If it does not exist, a new one will be generated.
334 func get_set_glossary_translation_id() -> String:
335 if _translation_id == null or _translation_id.is_empty():
338 return _translation_id