01 Feb

Extracting Localization Phrases from our Unity projects

In our Unity projects, we handle localization using the No Such Localization component, as detailed in a previous post. Specifically, we use the phrases version, where phrases act as keys to retrieve strings from a comprehensive table. Switching languages is as simple as changing the table for text (for images, we use a different method).

We maintain a large database of strings and extract a subset tailored to each game’s specific requirements. To do this, we compile a list of all the phrases used by the localization components within a given game. Our process is straightforward: we open each scene in the Unity project, retrieve all localization components, and store their phrases.

The following code demonstrates our approach. We’ve integrated this functionality into a Unity editor menu option, automating the entire process of retrieving phrases and writing them into a text file.

public class IKIGamesEditorTools
{
    private const string LocalizationMenuPath = "IKIGames/Localization/Extract All Phrases";
    private const string LocalizationFileName = "localization_keys.txt";

    [MenuItem(LocalizationMenuPath)]
    private static void ExtractAllLocalizationPhrasesInAllScenes()
    {
        HashSet<string> phrases = new HashSet<string>();
        List<string> scenePaths = GetScenePaths();

        foreach (var scenePath in scenePaths)
        {
            ExtractPhrasesFromScene(scenePath, phrases);
        }

        WritePhrases(phrases, LocalizationFileName);
    }

    private static List<string> GetScenePaths()
    {
        List<string> scenePaths = new List<string>();
        foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
        {
            scenePaths.Add(scene.path);
        }
        return scenePaths;
    }

    private static void ExtractPhrasesFromScene(string scenePath, HashSet<string> phrases)
    {
        UnityEditor.SceneManagement.EditorSceneManager.OpenScene(scenePath, UnityEditor.SceneManagement.OpenSceneMode.Single);
        ExtractPhrasesFromLocalizationComponentes(phrases);
        // No need to close the new scene since OpenSceneMode.Single opens it as the only active scene
        Debug.Log($"Processed: {scenePath}");
    }

    private static void ExtractPhrasesFromLocalizationComponentes(HashSet<string> phrases)
    {
        foreach (NoSuchStudio.Localization.Localizers.TMProTextLocalizer localizer in Resources.FindObjectsOfTypeAll<NoSuchStudio.Localization.Localizers.TMProTextLocalizer>())
        {
            if (!string.IsNullOrEmpty(localizer.phrase))
            {
                phrases.Add(localizer.phrase);
            }
        }
        foreach (NoSuchStudio.Localization.Localizers.TextLocalizer localizer in Resources.FindObjectsOfTypeAll<NoSuchStudio.Localization.Localizers.TextLocalizer>())
        {
            if (!string.IsNullOrEmpty(localizer.phrase))
            {
                phrases.Add(localizer.phrase);
            }
        }
    }

    private static void WritePhrases(HashSet<string> hashSet, string filename)
    {
        string filePath = Path.Combine(Application.dataPath, filename);
        try
        {
            using (StreamWriter writer = new StreamWriter(filePath))
            {
                foreach (string item in hashSet)
                {
                    writer.WriteLine(item);
                }
            }
            Debug.Log($"Phrases written successfully to: {filePath}");
        }
        catch (System.Exception ex)
        {
            Debug.LogError($"Failed to write phrases to file: {ex.Message}");
        }
    }
}