1 | /**************************************************************************/ |
2 | /* localization_editor.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "localization_editor.h" |
32 | |
33 | #include "core/config/project_settings.h" |
34 | #include "core/string/translation.h" |
35 | #include "editor/editor_scale.h" |
36 | #include "editor/editor_translation_parser.h" |
37 | #include "editor/editor_undo_redo_manager.h" |
38 | #include "editor/filesystem_dock.h" |
39 | #include "editor/gui/editor_file_dialog.h" |
40 | #include "editor/pot_generator.h" |
41 | #include "scene/gui/control.h" |
42 | |
43 | void LocalizationEditor::_notification(int p_what) { |
44 | switch (p_what) { |
45 | case NOTIFICATION_ENTER_TREE: { |
46 | translation_list->connect("button_clicked" , callable_mp(this, &LocalizationEditor::_translation_delete)); |
47 | translation_pot_list->connect("button_clicked" , callable_mp(this, &LocalizationEditor::_pot_delete)); |
48 | |
49 | List<String> tfn; |
50 | ResourceLoader::get_recognized_extensions_for_type("Translation" , &tfn); |
51 | for (const String &E : tfn) { |
52 | translation_file_open->add_filter("*." + E); |
53 | } |
54 | |
55 | List<String> rfn; |
56 | ResourceLoader::get_recognized_extensions_for_type("Resource" , &rfn); |
57 | for (const String &E : rfn) { |
58 | translation_res_file_open_dialog->add_filter("*." + E); |
59 | translation_res_option_file_open_dialog->add_filter("*." + E); |
60 | } |
61 | |
62 | _update_pot_file_extensions(); |
63 | pot_generate_dialog->add_filter("*.pot" ); |
64 | } break; |
65 | } |
66 | } |
67 | |
68 | void LocalizationEditor::add_translation(const String &p_translation) { |
69 | PackedStringArray translations; |
70 | translations.push_back(p_translation); |
71 | _translation_add(translations); |
72 | } |
73 | |
74 | void LocalizationEditor::_translation_add(const PackedStringArray &p_paths) { |
75 | PackedStringArray translations = GLOBAL_GET("internationalization/locale/translations" ); |
76 | for (int i = 0; i < p_paths.size(); i++) { |
77 | if (!translations.has(p_paths[i])) { |
78 | // Don't add duplicate translation paths. |
79 | translations.push_back(p_paths[i]); |
80 | } |
81 | } |
82 | |
83 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
84 | undo_redo->create_action(vformat(TTR("Add %d Translations" ), p_paths.size())); |
85 | undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/translations" , translations); |
86 | undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/translations" , GLOBAL_GET("internationalization/locale/translations" )); |
87 | undo_redo->add_do_method(this, "update_translations" ); |
88 | undo_redo->add_undo_method(this, "update_translations" ); |
89 | undo_redo->add_do_method(this, "emit_signal" , localization_changed); |
90 | undo_redo->add_undo_method(this, "emit_signal" , localization_changed); |
91 | undo_redo->commit_action(); |
92 | } |
93 | |
94 | void LocalizationEditor::_translation_file_open() { |
95 | translation_file_open->popup_file_dialog(); |
96 | } |
97 | |
98 | void LocalizationEditor::_translation_delete(Object *p_item, int p_column, int p_button, MouseButton p_mouse_button) { |
99 | if (p_mouse_button != MouseButton::LEFT) { |
100 | return; |
101 | } |
102 | |
103 | TreeItem *ti = Object::cast_to<TreeItem>(p_item); |
104 | ERR_FAIL_NULL(ti); |
105 | |
106 | int idx = ti->get_metadata(0); |
107 | |
108 | PackedStringArray translations = GLOBAL_GET("internationalization/locale/translations" ); |
109 | |
110 | ERR_FAIL_INDEX(idx, translations.size()); |
111 | |
112 | translations.remove_at(idx); |
113 | |
114 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
115 | undo_redo->create_action(TTR("Remove Translation" )); |
116 | undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/translations" , translations); |
117 | undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/translations" , GLOBAL_GET("internationalization/locale/translations" )); |
118 | undo_redo->add_do_method(this, "update_translations" ); |
119 | undo_redo->add_undo_method(this, "update_translations" ); |
120 | undo_redo->add_do_method(this, "emit_signal" , localization_changed); |
121 | undo_redo->add_undo_method(this, "emit_signal" , localization_changed); |
122 | undo_redo->commit_action(); |
123 | } |
124 | |
125 | void LocalizationEditor::_translation_res_file_open() { |
126 | translation_res_file_open_dialog->popup_file_dialog(); |
127 | } |
128 | |
129 | void LocalizationEditor::_translation_res_add(const PackedStringArray &p_paths) { |
130 | Variant prev; |
131 | Dictionary remaps; |
132 | |
133 | if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/translation_remaps" )) { |
134 | remaps = GLOBAL_GET("internationalization/locale/translation_remaps" ); |
135 | prev = remaps; |
136 | } |
137 | |
138 | for (int i = 0; i < p_paths.size(); i++) { |
139 | if (!remaps.has(p_paths[i])) { |
140 | // Don't overwrite with an empty remap array if an array already exists for the given path. |
141 | remaps[p_paths[i]] = PackedStringArray(); |
142 | } |
143 | } |
144 | |
145 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
146 | undo_redo->create_action(vformat(TTR("Translation Resource Remap: Add %d Path(s)" ), p_paths.size())); |
147 | undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/translation_remaps" , remaps); |
148 | undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/translation_remaps" , prev); |
149 | undo_redo->add_do_method(this, "update_translations" ); |
150 | undo_redo->add_undo_method(this, "update_translations" ); |
151 | undo_redo->add_do_method(this, "emit_signal" , localization_changed); |
152 | undo_redo->add_undo_method(this, "emit_signal" , localization_changed); |
153 | undo_redo->commit_action(); |
154 | } |
155 | |
156 | void LocalizationEditor::_translation_res_option_file_open() { |
157 | translation_res_option_file_open_dialog->popup_file_dialog(); |
158 | } |
159 | |
160 | void LocalizationEditor::_translation_res_option_add(const PackedStringArray &p_paths) { |
161 | ERR_FAIL_COND(!ProjectSettings::get_singleton()->has_setting("internationalization/locale/translation_remaps" )); |
162 | |
163 | Dictionary remaps = GLOBAL_GET("internationalization/locale/translation_remaps" ); |
164 | |
165 | TreeItem *k = translation_remap->get_selected(); |
166 | ERR_FAIL_NULL(k); |
167 | |
168 | String key = k->get_metadata(0); |
169 | |
170 | ERR_FAIL_COND(!remaps.has(key)); |
171 | PackedStringArray r = remaps[key]; |
172 | for (int i = 0; i < p_paths.size(); i++) { |
173 | r.push_back(p_paths[i] + ":" + "en" ); |
174 | } |
175 | remaps[key] = r; |
176 | |
177 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
178 | undo_redo->create_action(vformat(TTR("Translation Resource Remap: Add %d Remap(s)" ), p_paths.size())); |
179 | undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/translation_remaps" , remaps); |
180 | undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/translation_remaps" , GLOBAL_GET("internationalization/locale/translation_remaps" )); |
181 | undo_redo->add_do_method(this, "update_translations" ); |
182 | undo_redo->add_undo_method(this, "update_translations" ); |
183 | undo_redo->add_do_method(this, "emit_signal" , localization_changed); |
184 | undo_redo->add_undo_method(this, "emit_signal" , localization_changed); |
185 | undo_redo->commit_action(); |
186 | } |
187 | |
188 | void LocalizationEditor::_translation_res_select() { |
189 | if (updating_translations) { |
190 | return; |
191 | } |
192 | call_deferred(SNAME("update_translations" )); |
193 | } |
194 | |
195 | void LocalizationEditor::(bool p_arrow_clicked) { |
196 | TreeItem *ed = translation_remap_options->get_edited(); |
197 | ERR_FAIL_NULL(ed); |
198 | |
199 | locale_select->set_locale(ed->get_tooltip_text(1)); |
200 | locale_select->popup_locale_dialog(); |
201 | } |
202 | |
203 | void LocalizationEditor::_translation_res_option_selected(const String &p_locale) { |
204 | TreeItem *ed = translation_remap_options->get_edited(); |
205 | ERR_FAIL_NULL(ed); |
206 | |
207 | ed->set_text(1, TranslationServer::get_singleton()->get_locale_name(p_locale)); |
208 | ed->set_tooltip_text(1, p_locale); |
209 | |
210 | LocalizationEditor::_translation_res_option_changed(); |
211 | } |
212 | |
213 | void LocalizationEditor::_translation_res_option_changed() { |
214 | if (updating_translations) { |
215 | return; |
216 | } |
217 | |
218 | if (!ProjectSettings::get_singleton()->has_setting("internationalization/locale/translation_remaps" )) { |
219 | return; |
220 | } |
221 | |
222 | Dictionary remaps = GLOBAL_GET("internationalization/locale/translation_remaps" ); |
223 | |
224 | TreeItem *k = translation_remap->get_selected(); |
225 | ERR_FAIL_NULL(k); |
226 | TreeItem *ed = translation_remap_options->get_edited(); |
227 | ERR_FAIL_NULL(ed); |
228 | |
229 | String key = k->get_metadata(0); |
230 | int idx = ed->get_metadata(0); |
231 | String path = ed->get_metadata(1); |
232 | String locale = ed->get_tooltip_text(1); |
233 | |
234 | ERR_FAIL_COND(!remaps.has(key)); |
235 | PackedStringArray r = remaps[key]; |
236 | r.set(idx, path + ":" + locale); |
237 | remaps[key] = r; |
238 | |
239 | updating_translations = true; |
240 | |
241 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
242 | undo_redo->create_action(TTR("Change Resource Remap Language" )); |
243 | undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/translation_remaps" , remaps); |
244 | undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/translation_remaps" , GLOBAL_GET("internationalization/locale/translation_remaps" )); |
245 | undo_redo->add_do_method(this, "update_translations" ); |
246 | undo_redo->add_undo_method(this, "update_translations" ); |
247 | undo_redo->add_do_method(this, "emit_signal" , localization_changed); |
248 | undo_redo->add_undo_method(this, "emit_signal" , localization_changed); |
249 | undo_redo->commit_action(); |
250 | updating_translations = false; |
251 | } |
252 | |
253 | void LocalizationEditor::_translation_res_delete(Object *p_item, int p_column, int p_button, MouseButton p_mouse_button) { |
254 | if (updating_translations) { |
255 | return; |
256 | } |
257 | |
258 | if (p_mouse_button != MouseButton::LEFT) { |
259 | return; |
260 | } |
261 | |
262 | if (!ProjectSettings::get_singleton()->has_setting("internationalization/locale/translation_remaps" )) { |
263 | return; |
264 | } |
265 | |
266 | Dictionary remaps = GLOBAL_GET("internationalization/locale/translation_remaps" ); |
267 | |
268 | TreeItem *k = Object::cast_to<TreeItem>(p_item); |
269 | |
270 | String key = k->get_metadata(0); |
271 | ERR_FAIL_COND(!remaps.has(key)); |
272 | |
273 | remaps.erase(key); |
274 | |
275 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
276 | undo_redo->create_action(TTR("Remove Resource Remap" )); |
277 | undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/translation_remaps" , remaps); |
278 | undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/translation_remaps" , GLOBAL_GET("internationalization/locale/translation_remaps" )); |
279 | undo_redo->add_do_method(this, "update_translations" ); |
280 | undo_redo->add_undo_method(this, "update_translations" ); |
281 | undo_redo->add_do_method(this, "emit_signal" , localization_changed); |
282 | undo_redo->add_undo_method(this, "emit_signal" , localization_changed); |
283 | undo_redo->commit_action(); |
284 | } |
285 | |
286 | void LocalizationEditor::_translation_res_option_delete(Object *p_item, int p_column, int p_button, MouseButton p_mouse_button) { |
287 | if (updating_translations) { |
288 | return; |
289 | } |
290 | |
291 | if (p_mouse_button != MouseButton::LEFT) { |
292 | return; |
293 | } |
294 | |
295 | if (!ProjectSettings::get_singleton()->has_setting("internationalization/locale/translation_remaps" )) { |
296 | return; |
297 | } |
298 | |
299 | Dictionary remaps = GLOBAL_GET("internationalization/locale/translation_remaps" ); |
300 | |
301 | TreeItem *k = translation_remap->get_selected(); |
302 | ERR_FAIL_NULL(k); |
303 | TreeItem *ed = Object::cast_to<TreeItem>(p_item); |
304 | ERR_FAIL_NULL(ed); |
305 | |
306 | String key = k->get_metadata(0); |
307 | int idx = ed->get_metadata(0); |
308 | |
309 | ERR_FAIL_COND(!remaps.has(key)); |
310 | PackedStringArray r = remaps[key]; |
311 | ERR_FAIL_INDEX(idx, r.size()); |
312 | r.remove_at(idx); |
313 | remaps[key] = r; |
314 | |
315 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
316 | undo_redo->create_action(TTR("Remove Resource Remap Option" )); |
317 | undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/translation_remaps" , remaps); |
318 | undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/translation_remaps" , GLOBAL_GET("internationalization/locale/translation_remaps" )); |
319 | undo_redo->add_do_method(this, "update_translations" ); |
320 | undo_redo->add_undo_method(this, "update_translations" ); |
321 | undo_redo->add_do_method(this, "emit_signal" , localization_changed); |
322 | undo_redo->add_undo_method(this, "emit_signal" , localization_changed); |
323 | undo_redo->commit_action(); |
324 | } |
325 | |
326 | void LocalizationEditor::_pot_add(const PackedStringArray &p_paths) { |
327 | PackedStringArray pot_translations = GLOBAL_GET("internationalization/locale/translations_pot_files" ); |
328 | for (int i = 0; i < p_paths.size(); i++) { |
329 | if (!pot_translations.has(p_paths[i])) { |
330 | pot_translations.push_back(p_paths[i]); |
331 | } |
332 | } |
333 | |
334 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
335 | undo_redo->create_action(vformat(TTR("Add %d file(s) for POT generation" ), p_paths.size())); |
336 | undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/translations_pot_files" , pot_translations); |
337 | undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/translations_pot_files" , GLOBAL_GET("internationalization/locale/translations_pot_files" )); |
338 | undo_redo->add_do_method(this, "update_translations" ); |
339 | undo_redo->add_undo_method(this, "update_translations" ); |
340 | undo_redo->add_do_method(this, "emit_signal" , localization_changed); |
341 | undo_redo->add_undo_method(this, "emit_signal" , localization_changed); |
342 | undo_redo->commit_action(); |
343 | } |
344 | |
345 | void LocalizationEditor::_pot_delete(Object *p_item, int p_column, int p_button, MouseButton p_mouse_button) { |
346 | if (p_mouse_button != MouseButton::LEFT) { |
347 | return; |
348 | } |
349 | |
350 | TreeItem *ti = Object::cast_to<TreeItem>(p_item); |
351 | ERR_FAIL_NULL(ti); |
352 | |
353 | int idx = ti->get_metadata(0); |
354 | |
355 | PackedStringArray pot_translations = GLOBAL_GET("internationalization/locale/translations_pot_files" ); |
356 | |
357 | ERR_FAIL_INDEX(idx, pot_translations.size()); |
358 | |
359 | pot_translations.remove_at(idx); |
360 | |
361 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
362 | undo_redo->create_action(TTR("Remove file from POT generation" )); |
363 | undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/translations_pot_files" , pot_translations); |
364 | undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/translations_pot_files" , GLOBAL_GET("internationalization/locale/translations_pot_files" )); |
365 | undo_redo->add_do_method(this, "update_translations" ); |
366 | undo_redo->add_undo_method(this, "update_translations" ); |
367 | undo_redo->add_do_method(this, "emit_signal" , localization_changed); |
368 | undo_redo->add_undo_method(this, "emit_signal" , localization_changed); |
369 | undo_redo->commit_action(); |
370 | } |
371 | |
372 | void LocalizationEditor::_pot_file_open() { |
373 | pot_file_open_dialog->popup_file_dialog(); |
374 | } |
375 | |
376 | void LocalizationEditor::_pot_generate_open() { |
377 | pot_generate_dialog->popup_file_dialog(); |
378 | } |
379 | |
380 | void LocalizationEditor::_pot_generate(const String &p_file) { |
381 | POTGenerator::get_singleton()->generate_pot(p_file); |
382 | } |
383 | |
384 | void LocalizationEditor::_update_pot_file_extensions() { |
385 | pot_file_open_dialog->clear_filters(); |
386 | List<String> translation_parse_file_extensions; |
387 | EditorTranslationParser::get_singleton()->get_recognized_extensions(&translation_parse_file_extensions); |
388 | for (const String &E : translation_parse_file_extensions) { |
389 | pot_file_open_dialog->add_filter("*." + E); |
390 | } |
391 | } |
392 | |
393 | void LocalizationEditor::connect_filesystem_dock_signals(FileSystemDock *p_fs_dock) { |
394 | p_fs_dock->connect("files_moved" , callable_mp(this, &LocalizationEditor::_filesystem_files_moved)); |
395 | p_fs_dock->connect("file_removed" , callable_mp(this, &LocalizationEditor::_filesystem_file_removed)); |
396 | } |
397 | |
398 | void LocalizationEditor::_filesystem_files_moved(const String &p_old_file, const String &p_new_file) { |
399 | // Update remaps if the moved file is a part of them. |
400 | Dictionary remaps; |
401 | bool remaps_changed = false; |
402 | |
403 | if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/translation_remaps" )) { |
404 | remaps = GLOBAL_GET("internationalization/locale/translation_remaps" ); |
405 | } |
406 | |
407 | // Check for the keys. |
408 | if (remaps.has(p_old_file)) { |
409 | PackedStringArray remapped_files = remaps[p_old_file]; |
410 | remaps.erase(p_old_file); |
411 | remaps[p_new_file] = remapped_files; |
412 | remaps_changed = true; |
413 | print_verbose(vformat("Changed remap key \"%s\" to \"%s\" due to a moved file." , p_old_file, p_new_file)); |
414 | } |
415 | |
416 | // Check for the Array elements of the values. |
417 | Array remap_keys = remaps.keys(); |
418 | for (int i = 0; i < remap_keys.size(); i++) { |
419 | PackedStringArray remapped_files = remaps[remap_keys[i]]; |
420 | bool remapped_files_updated = false; |
421 | |
422 | for (int j = 0; j < remapped_files.size(); j++) { |
423 | int splitter_pos = remapped_files[j].rfind(":" ); |
424 | String res_path = remapped_files[j].substr(0, splitter_pos); |
425 | |
426 | if (res_path == p_old_file) { |
427 | String locale_name = remapped_files[j].substr(splitter_pos + 1); |
428 | // Replace the element at that index. |
429 | remapped_files.insert(j, p_new_file + ":" + locale_name); |
430 | remapped_files.remove_at(j + 1); |
431 | remaps_changed = true; |
432 | remapped_files_updated = true; |
433 | print_verbose(vformat("Changed remap value \"%s\" to \"%s\" of key \"%s\" due to a moved file." , res_path + ":" + locale_name, remapped_files[j], remap_keys[i])); |
434 | } |
435 | } |
436 | |
437 | if (remapped_files_updated) { |
438 | remaps[remap_keys[i]] = remapped_files; |
439 | } |
440 | } |
441 | |
442 | if (remaps_changed) { |
443 | ProjectSettings::get_singleton()->set_setting("internationalization/locale/translation_remaps" , remaps); |
444 | update_translations(); |
445 | emit_signal("localization_changed" ); |
446 | } |
447 | } |
448 | |
449 | void LocalizationEditor::_filesystem_file_removed(const String &p_file) { |
450 | // Check if the remaps are affected. |
451 | Dictionary remaps; |
452 | |
453 | if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/translation_remaps" )) { |
454 | remaps = GLOBAL_GET("internationalization/locale/translation_remaps" ); |
455 | } |
456 | |
457 | bool remaps_changed = remaps.has(p_file); |
458 | |
459 | if (!remaps_changed) { |
460 | Array remap_keys = remaps.keys(); |
461 | for (int i = 0; i < remap_keys.size() && !remaps_changed; i++) { |
462 | PackedStringArray remapped_files = remaps[remap_keys[i]]; |
463 | for (int j = 0; j < remapped_files.size() && !remaps_changed; j++) { |
464 | int splitter_pos = remapped_files[j].rfind(":" ); |
465 | String res_path = remapped_files[j].substr(0, splitter_pos); |
466 | remaps_changed = p_file == res_path; |
467 | if (remaps_changed) { |
468 | print_verbose(vformat("Remap value \"%s\" of key \"%s\" has been removed from the file system." , remapped_files[j], remap_keys[i])); |
469 | } |
470 | } |
471 | } |
472 | } else { |
473 | print_verbose(vformat("Remap key \"%s\" has been removed from the file system." , p_file)); |
474 | } |
475 | |
476 | if (remaps_changed) { |
477 | update_translations(); |
478 | emit_signal("localization_changed" ); |
479 | } |
480 | } |
481 | |
482 | void LocalizationEditor::update_translations() { |
483 | if (updating_translations) { |
484 | return; |
485 | } |
486 | |
487 | updating_translations = true; |
488 | |
489 | translation_list->clear(); |
490 | TreeItem *root = translation_list->create_item(nullptr); |
491 | translation_list->set_hide_root(true); |
492 | if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/translations" )) { |
493 | PackedStringArray translations = GLOBAL_GET("internationalization/locale/translations" ); |
494 | for (int i = 0; i < translations.size(); i++) { |
495 | TreeItem *t = translation_list->create_item(root); |
496 | t->set_editable(0, false); |
497 | t->set_text(0, translations[i].replace_first("res://" , "" )); |
498 | t->set_tooltip_text(0, translations[i]); |
499 | t->set_metadata(0, i); |
500 | t->add_button(0, get_editor_theme_icon(SNAME("Remove" )), 0, false, TTR("Remove" )); |
501 | } |
502 | } |
503 | |
504 | // Update translation remaps. |
505 | String remap_selected; |
506 | if (translation_remap->get_selected()) { |
507 | remap_selected = translation_remap->get_selected()->get_metadata(0); |
508 | } |
509 | |
510 | translation_remap->clear(); |
511 | translation_remap_options->clear(); |
512 | root = translation_remap->create_item(nullptr); |
513 | TreeItem *root2 = translation_remap_options->create_item(nullptr); |
514 | translation_remap->set_hide_root(true); |
515 | translation_remap_options->set_hide_root(true); |
516 | translation_res_option_add_button->set_disabled(true); |
517 | |
518 | if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/translation_remaps" )) { |
519 | Dictionary remaps = GLOBAL_GET("internationalization/locale/translation_remaps" ); |
520 | List<Variant> rk; |
521 | remaps.get_key_list(&rk); |
522 | Vector<String> keys; |
523 | for (const Variant &E : rk) { |
524 | keys.push_back(E); |
525 | } |
526 | keys.sort(); |
527 | |
528 | for (int i = 0; i < keys.size(); i++) { |
529 | TreeItem *t = translation_remap->create_item(root); |
530 | t->set_editable(0, false); |
531 | t->set_text(0, keys[i].replace_first("res://" , "" )); |
532 | t->set_tooltip_text(0, keys[i]); |
533 | t->set_metadata(0, keys[i]); |
534 | t->add_button(0, get_editor_theme_icon(SNAME("Remove" )), 0, false, TTR("Remove" )); |
535 | |
536 | // Display that it has been removed if this is the case. |
537 | if (!FileAccess::exists(keys[i])) { |
538 | t->set_text(0, t->get_text(0) + vformat(" (%s)" , TTR("Removed" ))); |
539 | t->set_tooltip_text(0, vformat(TTR("%s cannot be found." ), t->get_tooltip_text(0))); |
540 | } |
541 | |
542 | if (keys[i] == remap_selected) { |
543 | t->select(0); |
544 | translation_res_option_add_button->set_disabled(false); |
545 | |
546 | PackedStringArray selected = remaps[keys[i]]; |
547 | for (int j = 0; j < selected.size(); j++) { |
548 | String s2 = selected[j]; |
549 | int qp = s2.rfind(":" ); |
550 | String path = s2.substr(0, qp); |
551 | String locale = s2.substr(qp + 1, s2.length()); |
552 | |
553 | TreeItem *t2 = translation_remap_options->create_item(root2); |
554 | t2->set_editable(0, false); |
555 | t2->set_text(0, path.replace_first("res://" , "" )); |
556 | t2->set_tooltip_text(0, path); |
557 | t2->set_metadata(0, j); |
558 | t2->add_button(0, get_editor_theme_icon(SNAME("Remove" )), 0, false, TTR("Remove" )); |
559 | t2->set_cell_mode(1, TreeItem::CELL_MODE_CUSTOM); |
560 | t2->set_text(1, TranslationServer::get_singleton()->get_locale_name(locale)); |
561 | t2->set_editable(1, true); |
562 | t2->set_metadata(1, path); |
563 | t2->set_tooltip_text(1, locale); |
564 | |
565 | // Display that it has been removed if this is the case. |
566 | if (!FileAccess::exists(path)) { |
567 | t2->set_text(0, t2->get_text(0) + vformat(" (%s)" , TTR("Removed" ))); |
568 | t2->set_tooltip_text(0, vformat(TTR("%s cannot be found." ), t2->get_tooltip_text(0))); |
569 | } |
570 | } |
571 | } |
572 | } |
573 | } |
574 | |
575 | // Update translation POT files. |
576 | translation_pot_list->clear(); |
577 | root = translation_pot_list->create_item(nullptr); |
578 | translation_pot_list->set_hide_root(true); |
579 | PackedStringArray pot_translations = GLOBAL_GET("internationalization/locale/translations_pot_files" ); |
580 | for (int i = 0; i < pot_translations.size(); i++) { |
581 | TreeItem *t = translation_pot_list->create_item(root); |
582 | t->set_editable(0, false); |
583 | t->set_text(0, pot_translations[i].replace_first("res://" , "" )); |
584 | t->set_tooltip_text(0, pot_translations[i]); |
585 | t->set_metadata(0, i); |
586 | t->add_button(0, get_editor_theme_icon(SNAME("Remove" )), 0, false, TTR("Remove" )); |
587 | } |
588 | |
589 | // New translation parser plugin might extend possible file extensions in POT generation. |
590 | _update_pot_file_extensions(); |
591 | |
592 | pot_generate_button->set_disabled(pot_translations.is_empty()); |
593 | |
594 | updating_translations = false; |
595 | } |
596 | |
597 | void LocalizationEditor::_bind_methods() { |
598 | ClassDB::bind_method(D_METHOD("update_translations" ), &LocalizationEditor::update_translations); |
599 | |
600 | ADD_SIGNAL(MethodInfo("localization_changed" )); |
601 | } |
602 | |
603 | LocalizationEditor::LocalizationEditor() { |
604 | localization_changed = "localization_changed" ; |
605 | |
606 | TabContainer *translations = memnew(TabContainer); |
607 | translations->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
608 | add_child(translations); |
609 | |
610 | { |
611 | VBoxContainer *tvb = memnew(VBoxContainer); |
612 | tvb->set_name(TTR("Translations" )); |
613 | translations->add_child(tvb); |
614 | |
615 | HBoxContainer *thb = memnew(HBoxContainer); |
616 | Label *l = memnew(Label(TTR("Translations:" ))); |
617 | l->set_theme_type_variation("HeaderSmall" ); |
618 | thb->add_child(l); |
619 | thb->add_spacer(); |
620 | tvb->add_child(thb); |
621 | |
622 | Button *addtr = memnew(Button(TTR("Add..." ))); |
623 | addtr->connect("pressed" , callable_mp(this, &LocalizationEditor::_translation_file_open)); |
624 | thb->add_child(addtr); |
625 | |
626 | VBoxContainer *tmc = memnew(VBoxContainer); |
627 | tmc->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
628 | tvb->add_child(tmc); |
629 | |
630 | translation_list = memnew(Tree); |
631 | translation_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
632 | tmc->add_child(translation_list); |
633 | |
634 | locale_select = memnew(EditorLocaleDialog); |
635 | locale_select->connect("locale_selected" , callable_mp(this, &LocalizationEditor::_translation_res_option_selected)); |
636 | add_child(locale_select); |
637 | |
638 | translation_file_open = memnew(EditorFileDialog); |
639 | translation_file_open->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES); |
640 | translation_file_open->connect("files_selected" , callable_mp(this, &LocalizationEditor::_translation_add)); |
641 | add_child(translation_file_open); |
642 | } |
643 | |
644 | { |
645 | VBoxContainer *tvb = memnew(VBoxContainer); |
646 | tvb->set_name(TTR("Remaps" )); |
647 | translations->add_child(tvb); |
648 | |
649 | HBoxContainer *thb = memnew(HBoxContainer); |
650 | Label *l = memnew(Label(TTR("Resources:" ))); |
651 | l->set_theme_type_variation("HeaderSmall" ); |
652 | thb->add_child(l); |
653 | thb->add_spacer(); |
654 | tvb->add_child(thb); |
655 | |
656 | Button *addtr = memnew(Button(TTR("Add..." ))); |
657 | addtr->connect("pressed" , callable_mp(this, &LocalizationEditor::_translation_res_file_open)); |
658 | thb->add_child(addtr); |
659 | |
660 | VBoxContainer *tmc = memnew(VBoxContainer); |
661 | tmc->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
662 | tvb->add_child(tmc); |
663 | |
664 | translation_remap = memnew(Tree); |
665 | translation_remap->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
666 | translation_remap->connect("cell_selected" , callable_mp(this, &LocalizationEditor::_translation_res_select)); |
667 | translation_remap->connect("button_clicked" , callable_mp(this, &LocalizationEditor::_translation_res_delete)); |
668 | tmc->add_child(translation_remap); |
669 | |
670 | translation_res_file_open_dialog = memnew(EditorFileDialog); |
671 | translation_res_file_open_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES); |
672 | translation_res_file_open_dialog->connect("files_selected" , callable_mp(this, &LocalizationEditor::_translation_res_add)); |
673 | add_child(translation_res_file_open_dialog); |
674 | |
675 | thb = memnew(HBoxContainer); |
676 | l = memnew(Label(TTR("Remaps by Locale:" ))); |
677 | l->set_theme_type_variation("HeaderSmall" ); |
678 | thb->add_child(l); |
679 | thb->add_spacer(); |
680 | tvb->add_child(thb); |
681 | |
682 | addtr = memnew(Button(TTR("Add..." ))); |
683 | addtr->connect("pressed" , callable_mp(this, &LocalizationEditor::_translation_res_option_file_open)); |
684 | translation_res_option_add_button = addtr; |
685 | thb->add_child(addtr); |
686 | |
687 | tmc = memnew(VBoxContainer); |
688 | tmc->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
689 | tvb->add_child(tmc); |
690 | |
691 | translation_remap_options = memnew(Tree); |
692 | translation_remap_options->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
693 | translation_remap_options->set_columns(2); |
694 | translation_remap_options->set_column_title(0, TTR("Path" )); |
695 | translation_remap_options->set_column_title(1, TTR("Locale" )); |
696 | translation_remap_options->set_column_titles_visible(true); |
697 | translation_remap_options->set_column_expand(0, true); |
698 | translation_remap_options->set_column_clip_content(0, true); |
699 | translation_remap_options->set_column_expand(1, false); |
700 | translation_remap_options->set_column_clip_content(1, false); |
701 | translation_remap_options->set_column_custom_minimum_width(1, 250); |
702 | translation_remap_options->connect("item_edited" , callable_mp(this, &LocalizationEditor::_translation_res_option_changed)); |
703 | translation_remap_options->connect("button_clicked" , callable_mp(this, &LocalizationEditor::_translation_res_option_delete)); |
704 | translation_remap_options->connect("custom_popup_edited" , callable_mp(this, &LocalizationEditor::_translation_res_option_popup)); |
705 | tmc->add_child(translation_remap_options); |
706 | |
707 | translation_res_option_file_open_dialog = memnew(EditorFileDialog); |
708 | translation_res_option_file_open_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES); |
709 | translation_res_option_file_open_dialog->connect("files_selected" , callable_mp(this, &LocalizationEditor::_translation_res_option_add)); |
710 | add_child(translation_res_option_file_open_dialog); |
711 | } |
712 | |
713 | { |
714 | VBoxContainer *tvb = memnew(VBoxContainer); |
715 | tvb->set_name(TTR("POT Generation" )); |
716 | translations->add_child(tvb); |
717 | |
718 | HBoxContainer *thb = memnew(HBoxContainer); |
719 | Label *l = memnew(Label(TTR("Files with translation strings:" ))); |
720 | l->set_theme_type_variation("HeaderSmall" ); |
721 | thb->add_child(l); |
722 | thb->add_spacer(); |
723 | tvb->add_child(thb); |
724 | |
725 | Button *addtr = memnew(Button(TTR("Add..." ))); |
726 | addtr->connect("pressed" , callable_mp(this, &LocalizationEditor::_pot_file_open)); |
727 | thb->add_child(addtr); |
728 | |
729 | pot_generate_button = memnew(Button(TTR("Generate POT" ))); |
730 | pot_generate_button->connect("pressed" , callable_mp(this, &LocalizationEditor::_pot_generate_open)); |
731 | thb->add_child(pot_generate_button); |
732 | |
733 | VBoxContainer *tmc = memnew(VBoxContainer); |
734 | tmc->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
735 | tvb->add_child(tmc); |
736 | |
737 | translation_pot_list = memnew(Tree); |
738 | translation_pot_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
739 | tmc->add_child(translation_pot_list); |
740 | |
741 | pot_generate_dialog = memnew(EditorFileDialog); |
742 | pot_generate_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); |
743 | pot_generate_dialog->connect("file_selected" , callable_mp(this, &LocalizationEditor::_pot_generate)); |
744 | add_child(pot_generate_dialog); |
745 | |
746 | pot_file_open_dialog = memnew(EditorFileDialog); |
747 | pot_file_open_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES); |
748 | pot_file_open_dialog->connect("files_selected" , callable_mp(this, &LocalizationEditor::_pot_add)); |
749 | add_child(pot_file_open_dialog); |
750 | } |
751 | } |
752 | |