]> Untitled Git - wolf-seeking-sheep.git/blob - addons/dialogic/Modules/Glossary/glossary_resource.gd
Squashed commit of the following:
[wolf-seeking-sheep.git] / addons / dialogic / Modules / Glossary / glossary_resource.gd
1 @tool
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
5 extends Resource
6
7 ## Stores all entries for the glossary.
8 ##
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 := {}
14
15 ## If false, no entries from this glossary will be shown
16 @export var enabled: bool = true
17
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 := "_"
39
40
41 ## Private ID assigned when this glossary is translated.
42 @export var _translation_id := ""
43
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
46 ## be shown.
47 ## The values (String) are the translation ID.
48 @export var _translation_keys := {}
49
50
51
52 ## Removes an entry and all its aliases (alternative property) from
53 ## the glossary.
54 ## [param entry_key] may be an entry name or an alias.
55 ##
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)
59
60         if entry.is_empty():
61                 return false
62
63         var aliases: Array = entry.get(ALTERNATIVE_PROPERTY, [])
64
65         for alias: String in aliases:
66                 _remove_entry_alias(alias)
67
68         entries.erase(entry_key)
69
70         return true
71
72
73 ## This is an internal method.
74 ## Erases an entry alias key based the given [param entry_key].
75 ##
76 ## Returns true if [param entry_key] lead to a value and the value
77 ## was an alias.
78 ##
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)
82
83         if value == null or value is Dictionary:
84                 return false
85
86         entries.erase(entry_key)
87
88         return true
89
90
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.
94 ##
95 ## This method fails if the [param old_entry_key] does not exist.
96
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)
103
104         if entry == null:
105                 return
106
107         entry.name = new_entry_key
108
109         entries.erase(old_entry_key)
110         entries[new_entry_key] = entry
111
112
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, {})
118
119         # Handle alias value.
120         if entry is String:
121                 entry = entries.get(entry, {})
122
123         return entry
124
125
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.
129 ##
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)
134
135         if not entry.is_empty() and alias_entry.is_empty():
136                 entries[alias] = entry_key
137                 return true
138
139         return false
140
141
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]
146
147         if entries.has(entry_key):
148                 return false
149
150         entries[entry_key] = entry
151
152         for alternative: String in entry.get(ALTERNATIVE_PROPERTY, []):
153                 entries[alternative.strip_edges()] = entry_key
154
155         return true
156
157
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 = []
163
164         var translation_enabled: bool = ProjectSettings.get_setting("dialogic/translation/enabled", false)
165
166         if not translation_enabled:
167                 word_options.append(entry_key)
168
169                 for alternative: String in get_entry(entry_key).get(ALTERNATIVE_PROPERTY, []):
170                         word_options.append(alternative.strip_edges())
171
172                 return word_options
173
174         var translation_entry_key_id: String = get_property_translation_key(entry_key, NAME_PROPERTY)
175
176         if translation_entry_key_id.is_empty():
177                 return []
178
179         var translated_entry_key := tr(translation_entry_key_id)
180
181         if not translated_entry_key == translation_entry_key_id:
182                 word_options.append(translated_entry_key)
183
184         var translation_alternatives_id: String = get_property_translation_key(entry_key, ALTERNATIVE_PROPERTY)
185         var translated_alternatives_str := tr(translation_alternatives_id)
186
187         if not translated_alternatives_str == translation_alternatives_id:
188                 var translated_alternatives := translated_alternatives_str.split(",")
189
190                 for alternative: String in translated_alternatives:
191                         word_options.append(alternative.strip_edges())
192
193         return word_options
194
195
196 ## Gets the regex option for the given [param entry_key].
197 ## If the regex option does not exist, it will be generated.
198 ##
199 ## A regex option is the accumulation of valid words that can trigger the
200 ## glossary popup.
201 ##
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)
205
206         var regex_options: Dictionary = entry.get(REGEX_OPTION_PROPERTY, {})
207
208         if regex_options.is_empty():
209                 entry[REGEX_OPTION_PROPERTY] = regex_options
210
211         var locale_key: String = TranslationServer.get_locale()
212         var regex_option: String = regex_options.get(locale_key, "")
213
214         if not regex_option.is_empty():
215                 return regex_option
216
217         var word_options: Array = _get_word_options(entry_key)
218         regex_option = "|".join(word_options)
219
220         regex_options[locale_key] = regex_option
221
222         return regex_option
223
224
225 #region ADD AND CLEAR TRANSLATION KEYS
226
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
231
232
233 ## Removes the translation ID of this glossary.
234 func remove_translation_id() -> void:
235         _translation_id = ""
236
237
238 ## Removes the translation ID of all glossary entries.
239 func remove_entry_translation_ids() -> void:
240         for entry: Variant in entries.values():
241
242                 # Ignore aliases.
243                 if entry is String:
244                         continue
245
246                 if entry.has(TRANSLATION_PROPERTY):
247                         entry[TRANSLATION_PROPERTY] = ""
248
249
250 ## Clears the lookup tables using translation keys.
251 func clear_translation_keys() -> void:
252         const RESOURCE_NAME_KEY := RESOURCE_NAME + "/"
253
254         for translation_key: String in entries.keys():
255
256                 if translation_key.begins_with(RESOURCE_NAME_KEY):
257                         entries.erase(translation_key)
258
259         _translation_keys.clear()
260
261 #endregion
262
263
264 #region GET AND SET TRANSLATION IDS AND KEYS
265
266 ## Returns a key used to reference this glossary in the translation CSV file.
267 ##
268 ## Time complexity: O(1)
269 func get_property_translation_key(entry_key: String, property: String) -> String:
270         var entry := get_entry(entry_key)
271
272         if entry == null:
273                 return ""
274
275         var entry_translation_key: String = entry.get(TRANSLATION_PROPERTY, "")
276
277         if entry_translation_key.is_empty() or _translation_id.is_empty():
278                 return ""
279
280         var glossary_csv_key := (RESOURCE_NAME
281                 .path_join(_translation_id)
282                 .path_join(entry_translation_key)
283                 .path_join(property))
284
285         return glossary_csv_key
286
287
288
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:
293         return (
294                 DialogicGlossary.RESOURCE_NAME
295                         .path_join(_translation_id)
296         )
297
298
299 ## Returns the translation key for the given [param glossary_translation_id] and
300 ## [param entry_translation_id].
301 ##
302 ## By key, we refer to the uniquely named property per translation entry.
303 ##
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:
306         return (
307                 DialogicGlossary.RESOURCE_NAME
308                         .path_join(_translation_id)
309                         .path_join(entry_translation_id)
310                         .path_join(property)
311         )
312
313
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 := ""
319
320         var glossary_translation_id: String = glossary_entry.get(TRANSLATION_PROPERTY, "")
321
322         if glossary_translation_id.is_empty():
323                 entry_translation_id = DialogicUtil.get_next_translation_id()
324                 glossary_entry[TRANSLATION_PROPERTY] = entry_translation_id
325
326         else:
327                 entry_translation_id = glossary_entry[TRANSLATION_PROPERTY]
328
329         return entry_translation_id
330
331
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():
336                 add_translation_id()
337
338         return _translation_id
339
340 #endregion