Repository: godotengine/godot (language: cpp)

Issue:
Translation CSV importer produces invalid or duplicate UIDs
### Tested versions

- Reproducible in all 4.4.beta (tested in 4.4beta4)
- Not present in 4.3 or earlier.

### System information

Godot v4.4.beta4 - Windows 10 (build 19045) - Single-window, 2 monitors - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 3090 (NVIDIA; 32.0.15.6094) - Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz (16 threads)

### Issue description

Two bugs with the translation csv importer. Both of these are regression in Godot 4.4 due to the new consistent UID hashing added in PR #97363

First, it can produce negative UIDs, which will serialize as `uid://<invalid>`. Negative UIDs are currently not permitted for text serialization, though they work fine in binary serialization.

Second, it does not check if the UID already exists in the project. This causes a few glitches which are hard to verify, for example referencing one file may reference the wrong copy (it might be random which gets referenced due to the nature of duplicate UIDs in Godot).

Also, note that reproducing this in a fresh project will not be obvious, because Godot caches incorrect UIDs due to a bug with how `ResourceSaver::set_uid()` interacts with the editor's UID cache. You must close the editor and delete .godot to observe the new UIDs. This whole thing with set_uid probably belongs in its own issue.

### Steps to reproduce

1. Open attached MRP.
2. Open `reference_duplicate_uid.tscn` and press ctrl-s to resave scene.
3. Open reference_duplicate_uid.tscn` in text editor and note that both `test_translation.es.translation` and `test_translation.es_1.translation` have the same UIDs
4. Open translation_ref.tscn and press ctrl-s to resave scene
5. Check `translation_ref.tscn` in text editor and note `uid://<invalid>` for `test_translation.en.translation`

Optional steps to demonstrate that the invalid only happens on .tscn but not .scn:
6. Open translation_ref.scn and press ctrl-s to resave scene. This will not produce an invalid uid, but we cannot read this in a text editor.
7. Rename .en.translation outside godot and then open both `translation_ref.scn` and `translation_ref.tscn` The <invalid> only occurs for .tscn because binary resources permit negative UIDs. The .scn version will update the reference correctly.

### Minimal reproduction project (MRP)

[translation_csv_uids.zip](https://github.com/user-attachments/files/18903965/translation_csv_uids.zip)


Discussion / hints:

Fix patch:
diff --git a/editor/import/resource_importer_csv_translation.cpp b/editor/import/resource_importer_csv_translation.cpp
index 17f6070d3518..62aa7bd128f3 100644
--- a/editor/import/resource_importer_csv_translation.cpp
+++ b/editor/import/resource_importer_csv_translation.cpp
@@ -142,14 +142,22 @@ Error ResourceImporterCSVTranslation::import(ResourceUID::ID p_source_id, const
 		}
 
 		String save_path = p_source_file.get_basename() + "." + translations[i]->get_locale() + ".translation";
+		ResourceUID::ID save_id = hash64_murmur3_64(translations[i]->get_locale().hash64(), p_source_id) & 0x7FFFFFFFFFFFFFFF;
+		bool uid_already_exists = ResourceUID::get_singleton()->has_id(save_id);
+		if (uid_already_exists) {
+			// Avoid creating a new file with a duplicate UID.
+			// Always use this UID, even if the user has moved it to a different path.
+			save_path = ResourceUID::get_singleton()->get_id_path(save_id);
+		}
 
 		ResourceSaver::save(xlt, save_path);
 		if (r_gen_files) {
 			r_gen_files->push_back(save_path);
 		}
-
-		ResourceUID::ID save_id = hash64_murmur3_64(translations[i]->get_locale().hash64(), p_source_id);
-		ResourceSaver::set_uid(save_path, save_id);
+		if (!uid_already_exists) {
+			// No need to call set_uid if save_path already refers to save_id.
+			ResourceSaver::set_uid(save_path, save_id);
+		}
 	}
 
 	return OK;


Test patch: