Example #1
0
        public void YarnImporter_OnValidYarnFile_GetExpectedStrings()
        {
            string fileName = Path.GetRandomFileName();
            List <StringTableEntry> expectedStrings = GetExpectedStrings(fileName);

            string path = Application.dataPath + "/" + fileName + ".yarn";

            createdFilePaths.Add(path);

            File.WriteAllText(path, TestYarnScriptSource);
            AssetDatabase.Refresh();
            var result = AssetImporter.GetAtPath("Assets/" + fileName + ".yarn") as YarnImporter;

            // Importing this Yarn script will have produced a CSV
            // TextAsset containing the string table extracted from this
            // script. Parse that - we'll check that it contains what we
            // expect.
            var generatedStringsTable = StringTableEntry.ParseFromCSV(result.baseLanguage.text);

            // Simplify the results so that we can compare these string
            // table entries based only on specific fields
            System.Func <StringTableEntry, (string id, string text)> simplifier = e => (id : e.ID, text : e.Text);
            var simpleResult   = expectedStrings.Select(simplifier);
            var simpleExpected = generatedStringsTable.Select(simplifier);

            Assert.AreEqual(simpleExpected, simpleResult);
        }
Example #2
0
    public static void UpdateContents(LocalizationDatabase database)
    {
        var allTextAssets = database.TrackedPrograms
                            .SelectMany(p => p.localizations)
                            .Select(localization => new
        {
            localization.languageName,
            localization.text
        });

        foreach (var localization in database.Localizations)
        {
            if (localization == null)
            {
                // Ignore any null entries
                continue;
            }
            localization.Clear();
        }

        foreach (var localizedTextAsset in allTextAssets)
        {
            string languageName = localizedTextAsset.languageName;

            Localization localization;

            try
            {
                localization = database.GetLocalization(languageName);
            }
            catch (KeyNotFoundException)
            {
                Debug.LogWarning($"{localizedTextAsset.text.name} is marked for language {languageName}, but this {nameof(LocalizationDatabase)} isn't set up for that language. TODO: offer a quick way to create one here.");
                continue;
            }



            TextAsset textAsset = localizedTextAsset.text;

            if (textAsset == null)
            {
                // A null reference. Early out here.
                continue;
            }

            var records = StringTableEntry.ParseFromCSV(textAsset.text);

            foreach (var record in records)
            {
                AddLineEntryToLocalization(localization, record);
            }

            EditorUtility.SetDirty(localization);
        }

        AssetDatabase.SaveAssets();
    }
        public void YarnImporterUtility_CanUpdateLocalizedCSVs_WhenBaseScriptChanges()
        {
            // Arrange:
            // Set up a project with a Yarn file filled with tagged lines.
            var project    = SetUpProject(TestYarnScriptSource);
            var importer   = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(project)) as YarnProjectImporter;
            var scriptPath = AssetDatabase.GetAssetPath(importer.sourceScripts[0]);

            var destinationStringsFilePath = "Assets/" + Path.GetRandomFileName() + ".csv";

            // Act:
            // Create a .CSV File, and add it to the Yarn project.
            YarnProjectUtility.WriteStringsFile(destinationStringsFilePath, importer);
            createdFilePaths.Add(destinationStringsFilePath);

            AssetDatabase.Refresh();

            var stringsAsset = AssetDatabase.LoadAssetAtPath <TextAsset>(destinationStringsFilePath);

            importer.languagesToSourceAssets.Add(new YarnProjectImporter.LanguageToSourceAsset {
                languageID = "test", stringsFile = stringsAsset
            });
            EditorUtility.SetDirty(importer);
            importer.SaveAndReimport();

            // Capture the strings tables. We'll use them later.
            var unmodifiedBaseStringsTable      = importer.GenerateStringsTable();
            var unmodifiedLocalizedStringsTable = StringTableEntry.ParseFromCSV(File.ReadAllText(destinationStringsFilePath));

            // Next, modify the original source script.
            File.WriteAllText(scriptPath, TestYarnScriptSourceModified);

            AssetDatabase.Refresh();

            // Finally, update the CSV.
            LogAssert.Expect(LogType.Log, $"Updated the following files: {destinationStringsFilePath}");
            YarnProjectUtility.UpdateLocalizationCSVs(importer);

            AssetDatabase.Refresh();

            // Doing it again should result in a no-op.
            LogAssert.Expect(LogType.Log, "No files needed updating.");
            YarnProjectUtility.UpdateLocalizationCSVs(importer);

            // Capture the updated strings tables, so we can compare them.
            var modifiedBaseStringsTable      = importer.GenerateStringsTable();
            var modifiedLocalizedStringsTable = StringTableEntry.ParseFromCSV(File.ReadAllText(destinationStringsFilePath));

            // Assert: verify the base language string table contains the
            // string table entries we expect, verify the localized string
            // table contains the string table entries we expect

            System.Func <StringTableEntry, string> CompareIDs   = t => t.ID;
            System.Func <StringTableEntry, string> CompareLocks = t => t.Lock;

            var tests = new[] {
Example #4
0
    /// <summary>
    /// Returns an <see cref="IEnumerable"/> containing the string table
    /// entries for the base language for the specified Yarn script.
    /// </summary>
    /// <param name="serializedObject">A serialized object that represents
    /// a <see cref="YarnProgram"/>.</param>
    /// <returns>The string table entries.</returns>
    private static IEnumerable <StringTableEntry> GetBaseLanguageStringsForSelectedObject(SerializedObject serializedObject)
    {
        var baseLanguageProperty = serializedObject.FindProperty("baseLanguage");

        // Get the TextAsset that contains the base string table CSV
        TextAsset textAsset = baseLanguageProperty.objectReferenceValue as TextAsset;

        if (textAsset == null)
        {
            throw new System.NullReferenceException($"The base language table asset for {serializedObject.targetObject.name} is either null or not a TextAsset. Did the script fail to compile?");
        }

        var baseLanguageTableText = textAsset.text;

        // Parse this CSV into StringTableEntry structs
        return(StringTableEntry.ParseFromCSV(baseLanguageTableText)
               .OrderBy(entry => entry.File)
               .ThenBy(entry => int.Parse(entry.LineNumber)));
    }
Example #5
0
        public void YarnImporterUtility_CanUpdateLocalizedCSVs_WhenBaseScriptChanges()
        {
            // Arrange: Import a yarn script and create a localization
            // database for it, create an alternate localization for it
            string fileName = Path.GetRandomFileName();
            string path     = Path.Combine("Assets", fileName + ".yarn");

            createdFilePaths.Add(path);
            File.WriteAllText(path, TestYarnScriptSource);
            AssetDatabase.Refresh();
            var importer = AssetImporter.GetAtPath(path) as YarnImporter;
            var importerSerializedObject = new SerializedObject(importer);

            YarnImporterUtility.CreateNewLocalizationDatabase(importerSerializedObject);
            var localizationDatabaseSerializedObject = new SerializedObject(importer.localizationDatabase);

            LocalizationDatabaseUtility.CreateLocalizationWithLanguage(localizationDatabaseSerializedObject, AlternateLocaleCode);

            YarnImporterUtility.CreateLocalizationForLanguageInProgram(importerSerializedObject, AlternateLocaleCode);

            var unmodifiedBaseStringsTable      = StringTableEntry.ParseFromCSV((importerSerializedObject.targetObject as YarnImporter).baseLanguage.text);
            var unmodifiedLocalizedStringsTable = StringTableEntry.ParseFromCSV((importerSerializedObject.targetObject as YarnImporter).localizations.First(l => l.languageName == AlternateLocaleCode).text.text);

            // Act: modify the imported script so that lines are added,
            // changed and deleted, and then update the localized CSV
            // programmatically

            File.WriteAllText(path, TestYarnScriptSourceModified);
            AssetDatabase.Refresh();
            YarnImporterUtility.UpdateLocalizationCSVs(importerSerializedObject);

            var modifiedBaseStringsTable      = StringTableEntry.ParseFromCSV((importerSerializedObject.targetObject as YarnImporter).baseLanguage.text);
            var modifiedLocalizedStringsTable = StringTableEntry.ParseFromCSV((importerSerializedObject.targetObject as YarnImporter).localizations.First(l => l.languageName == AlternateLocaleCode).text.text);

            // Assert: verify the base language string table contains the
            // string table entries we expect, verify the localized string
            // table contains the string table entries we expect

            System.Func <StringTableEntry, string> CompareIDs   = t => t.ID;
            System.Func <StringTableEntry, string> CompareLocks = t => t.Lock;

            var tests = new[] {
    public static void UpdateContents(LineDatabase database)
    {
        // First, get all scripts whose importers are configured to use
        // this database - we need to add them to our TrackedPrograms list

        foreach (var updatedGUID in database.RecentlyUpdatedGUIDs)
        {
            var path = AssetDatabase.GUIDToAssetPath(updatedGUID);

            if (string.IsNullOrEmpty(path))
            {
                // The corresponding asset can't be found! No-op.
                continue;
            }

            var importer = AssetImporter.GetAtPath(path);
            if (!(importer is YarnImporter yarnImporter))
            {
                Debug.LogWarning($"Yarn Spinner internal error: line database was told to load asset {path}, but this does not have a {nameof(YarnImporter)}. Ignoring.");
                continue;
            }

            var textAsset = AssetDatabase.LoadAssetAtPath <TextAsset>(path);

            if (textAsset == null)
            {
                Debug.LogWarning($"Yarn Spinner internal error: failed to get a {nameof(TextAsset)} at {path}. Ignoring.");
                continue;
            }

            if (yarnImporter.lineDatabase == database)
            {
                // We need to add or update content based on this asset.
                database.AddTrackedProject(textAsset);
            }
            else
            {
                // This asset used to refer to this database, but now no
                // longer does. Remove the reference.
                database.RemoveTrackedProject(textAsset);
            }
        }

        database.RecentlyUpdatedGUIDs.Clear();

        var allTrackedImporters = database.TrackedScripts
                                  .Select(p => AssetDatabase.GetAssetPath(p))
                                  .Select(path => AssetImporter.GetAtPath(path) as YarnImporter);

        var allLocalizationAssets = allTrackedImporters
                                    .Where(p => p != null)
                                    .SelectMany(p => p.AllLocalizations)
                                    .Select(localization => new
        {
            languageID = localization.languageName,
            text       = localization.text
        });

        // Erase the contents of all localizations, because we're about to
        // replace them
        foreach (var localization in database.Localizations)
        {
            if (localization == null)
            {
                // Ignore any null entries
                continue;
            }
            localization.Clear();
        }

        foreach (var localizedTextAsset in allLocalizationAssets)
        {
            string languageName = localizedTextAsset.languageID;

            Localization localization;

            try
            {
                localization = database.GetLocalization(languageName);
            }
            catch (KeyNotFoundException)
            {
                Debug.LogWarning($"{localizedTextAsset.text.name} is marked for language {languageName}, but this {nameof(LineDatabase)} isn't set up for that language. TODO: offer a quick way to create one here.");
                continue;
            }

            TextAsset textAsset = localizedTextAsset.text;

            if (textAsset == null)
            {
                // A null reference. Early out here.
                continue;
            }

            var records = StringTableEntry.ParseFromCSV(textAsset.text);

            foreach (var record in records)
            {
                AddLineEntryToLocalization(localization, record);
            }

            EditorUtility.SetDirty(localization);
        }

        AssetDatabase.SaveAssets();
    }
Example #7
0
    /// <summary>
    /// Verifies the TextAsset referred to by <paramref name="loc"/>, and
    /// updates it if necessary.
    /// </summary>
    /// <param name="baseLocalizationStrings">A collection of <see
    /// cref="StringTableEntry"/></param>
    /// <param name="language">The language that <paramref name="loc"/>
    /// provides strings for.false</param>
    /// <param name="loc">A TextAsset containing localized strings in CSV
    /// format.</param>
    /// <returns>Whether <paramref name="loc"/> was modified.</returns>
    private static bool UpdateLocalizationFile(IEnumerable <StringTableEntry> baseLocalizationStrings, string language, TextAsset loc)
    {
        var translatedStrings = StringTableEntry.ParseFromCSV(loc.text);

        // Convert both enumerables to dictionaries, for easier lookup
        var baseDictionary       = baseLocalizationStrings.ToDictionary(entry => entry.ID);
        var translatedDictionary = translatedStrings.ToDictionary(entry => entry.ID);

        // The list of line IDs present in each localisation
        var baseIDs       = baseLocalizationStrings.Select(entry => entry.ID);
        var translatedIDs = translatedStrings.Select(entry => entry.ID);

        // The list of line IDs that are ONLY present in each localisation
        var onlyInBaseIDs       = baseIDs.Except(translatedIDs);
        var onlyInTranslatedIDs = translatedIDs.Except(baseIDs);

        // Tracks if the translated localisation needed modifications
        // (either new lines added, old lines removed, or changed lines
        // flagged)
        var modificationsNeeded = false;

        // Remove every entry whose ID is only present in the translated
        // set. This entry has been removed from the base localization.
        foreach (var id in onlyInTranslatedIDs.ToList())
        {
            translatedDictionary.Remove(id);
            modificationsNeeded = true;
        }

        // Conversely, for every entry that is only present in the base
        // localisation, we need to create a new entry for it.
        foreach (var id in onlyInBaseIDs)
        {
            StringTableEntry baseEntry = baseDictionary[id];
            var newEntry = new StringTableEntry(baseEntry)
            {
                // Empty this text, so that it's apparent that a translated
                // version needs to be provided.
                Text = string.Empty,
            };
            translatedDictionary.Add(id, newEntry);
            modificationsNeeded = true;
        }

        // Finally, we need to check for any entries in the translated
        // localisation that:
        // 1. have the same line ID as one in the base, but
        // 2. have a different Lock (the hash of the text), which indicates
        //    that the base text has changed.

        // First, get the list of IDs that are in both base and translated,
        // and then filter this list to any where the lock values differ
        var outOfDateLockIDs = baseDictionary.Keys
                               .Intersect(translatedDictionary.Keys)
                               .Where(id => baseDictionary[id].Lock != translatedDictionary[id].Lock);

        // Now loop over all of these, and update our translated dictionary
        // to include a note that it needs attention
        foreach (var id in outOfDateLockIDs)
        {
            // Get the translated entry as it currently exists
            var entry = translatedDictionary[id];

            // Include a note that this entry is out of date
            entry.Text = $"(NEEDS UPDATE) {entry.Text}";

            // Update the lock to match the new one
            entry.Lock = baseDictionary[id].Lock;

            // Put this modified entry back in the table
            translatedDictionary[id] = entry;

            modificationsNeeded = true;
        }

        // We're all done!

        if (modificationsNeeded == false)
        {
            // No changes needed to be done to the translated string table
            // entries. Stop here.
            return(false);
        }

        // We need to produce a replacement CSV file for the translated
        // entries.

        var outputStringEntries = translatedDictionary.Values
                                  .OrderBy(entry => entry.File)
                                  .ThenBy(entry => int.Parse(entry.LineNumber));

        var outputCSV = StringTableEntry.CreateCSV(outputStringEntries);

        // Write out the replacement text to this existing file, replacing
        // its existing contents
        var outputFile = AssetDatabase.GetAssetPath(loc);

        File.WriteAllText(outputFile, outputCSV);

        // Tell the asset database that the file needs to be reimported
        AssetDatabase.ImportAsset(outputFile);

        // Signal that the file was changed
        return(true);
    }