-
-
Save forestrf/7b317d53ff98ff8e8ebfc51050c033ad to your computer and use it in GitHub Desktop.
Find localization entries that are not used
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Text; | |
| using UnityEditor; | |
| using UnityEditor.Localization; | |
| using UnityEditor.SceneManagement; | |
| using UnityEngine; | |
| using UnityEngine.Localization.Tables; | |
| using Object = UnityEngine.Object; | |
| public class FindUnusedLocalizationKeys : EditorWindow { | |
| HashSet<(string collectionName, long entryId)> m_FoundEntries; | |
| [MenuItem("Window/Asset Management/Localization unused key finder")] | |
| static void CreateWizard() { | |
| EditorWindow.GetWindow<FindUnusedLocalizationKeys>(); | |
| } | |
| void OnGUI() { | |
| if (GUILayout.Button("Search")) { | |
| Search(); | |
| } | |
| } | |
| void Search() { | |
| EditorUtility.DisplayProgressBar("Localization unused key finder", "Searching", 0); | |
| m_FoundEntries = new HashSet<(string collectionName, long entryId)>(); | |
| var sceneGUIDs = AssetDatabase.FindAssets("t:Scene"); | |
| string[] guids = new string[] { "t:Prefab", "t:ScriptableObject" }.SelectMany(e => AssetDatabase.FindAssets(e)).ToArray(); | |
| var i = 0; | |
| bool cancelled = false; | |
| try { | |
| for (int j = 0; j < sceneGUIDs.Length; j++) { | |
| var sceneGUID = sceneGUIDs[j]; | |
| var path = AssetDatabase.GUIDToAssetPath(sceneGUID); | |
| try { | |
| var scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Single); | |
| if (EditorUtility.DisplayCancelableProgressBar("Localization unused key finder", $"Checking scene [{j}/{sceneGUIDs.Length}]. {path}", j / (float) sceneGUIDs.Length)) { | |
| cancelled = true; | |
| break; | |
| } | |
| var components = FindObjectsOfType<GameObject>().SelectMany(t => t.GetComponents<Component>()).ToArray(); | |
| for (int k = 0; k < components.Length; k++) { | |
| if (i % 200 == 0 && EditorUtility.DisplayCancelableProgressBar("Localization unused key finder", $"(Pass 1/2) Checking scene [{j}/{sceneGUIDs.Length}], object [{k}/{components.Length}]. {path}", j / (float) sceneGUIDs.Length + k / (float) components.Length / sceneGUIDs.Length)) { | |
| cancelled = true; | |
| break; | |
| } | |
| ExtractTableReferences(components[k]); | |
| } | |
| if (cancelled) break; | |
| } | |
| catch (ArgumentException a) { | |
| // Scene may be read only. NEXT! | |
| } | |
| } | |
| for (int j = 0; j < guids.Length; j++) { | |
| if (cancelled) break; | |
| var guid = guids[j]; | |
| var path = AssetDatabase.GUIDToAssetPath(guid); | |
| var objs = AssetDatabase.LoadAllAssetsAtPath(path); | |
| for (int k = 0; k < objs.Length; k++) { | |
| if (i % 200 == 0 && EditorUtility.DisplayCancelableProgressBar("Localization unused key finder", $"(Pass 2/2) Checking rest of assets [{j}/{guids.Length}], component [{k}/{objs.Length}]. {path}", i / (float) sceneGUIDs.Length + j / (float) guids.Length / sceneGUIDs.Length + k / (float) guids.Length / sceneGUIDs.Length / objs.Length)) { | |
| cancelled = true; | |
| break; | |
| } | |
| var obj = objs[k]; | |
| ExtractTableReferences(obj); | |
| } | |
| } | |
| } | |
| finally { | |
| EditorUtility.ClearProgressBar(); | |
| } | |
| FindMissingEntries(); | |
| } | |
| void FindMissingEntries() { | |
| var sb = new StringBuilder(); | |
| foreach (var collection in LocalizationEditorSettings.GetStringTableCollections()) { | |
| foreach (var entry in collection.SharedData.Entries) { | |
| if (m_FoundEntries.Contains((collection.TableCollectionName, entry.Id))) { | |
| // Entry is used | |
| sb.AppendLine($"[X] YES USED - {collection.TableCollectionName} / {entry.Key}"); | |
| } | |
| else { | |
| // Entry is not used. | |
| sb.AppendLine($"[ ] NOT USED - {collection.TableCollectionName} / {entry.Key}"); | |
| } | |
| } | |
| } | |
| Debug.Log(sb.ToString()); | |
| } | |
| void ExtractTableReferences(Object unityObject) { | |
| if (unityObject == null) | |
| return; | |
| var so = new SerializedObject(unityObject); | |
| var itr = so.GetIterator(); | |
| // This is ultra slow | |
| while (itr.Next(true)) { | |
| if (itr.type == "LocalizedString") { | |
| if (itr.isArray) { | |
| for (int i = 0; i < itr.arraySize; i++) { | |
| Process(itr.GetArrayElementAtIndex(i)); | |
| } | |
| } | |
| else Process(itr); | |
| void Process(SerializedProperty itr) { | |
| var collectionNameProperty = itr.FindPropertyRelative("m_TableReference.m_TableCollectionName"); | |
| //if (collectionNameProperty == null) return; | |
| var collectionName = collectionNameProperty.stringValue; | |
| TableReference tableReference; | |
| if (collectionName.StartsWith("GUID:")) | |
| tableReference = Guid.Parse(collectionName.Substring("GUID:".Length, collectionName.Length - "GUID:".Length)); | |
| else | |
| tableReference = collectionName; | |
| var key = itr.FindPropertyRelative("m_TableEntryReference.m_Key"); | |
| var keyId = itr.FindPropertyRelative("m_TableEntryReference.m_KeyId"); | |
| TableEntryReference tableEntryReference; | |
| if (keyId.longValue == 0) | |
| tableEntryReference = key.stringValue; | |
| else | |
| tableEntryReference = keyId.longValue; | |
| var collection = LocalizationEditorSettings.GetStringTableCollection(tableReference); | |
| var stringTableEntry = collection?.SharedData.GetEntryFromReference(tableEntryReference); | |
| if (stringTableEntry != null) { | |
| m_FoundEntries.Add((collection.TableCollectionName, stringTableEntry.Id)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment