Example #1
0
    /// <summary>
    /// Creates a new localization CSV file for the specified object, for
    /// the given language.
    /// </summary>
    /// <param name="serializedObject">A serialized object that represents
    /// a <see cref="YarnImporter"/>.</param>
    /// <param name="language">The language to generate a localization CSV
    /// for.</param>
    internal static void CreateLocalizationForLanguageInProgram(SerializedObject serializedObject, string language)
    {
        if (serializedObject.isEditingMultipleObjects)
        {
            Debug.LogError($"{nameof(CreateLocalizationForLanguageInProgram)} was called, but multiple objects were selected. Select a single object and try again.");
            return;
        }

        IEnumerable <StringTableEntry> baseLanguageStrings = GetBaseLanguageStringsForSelectedObject(serializedObject);

        // Produce a new version of these string entries, but with
        // different languages
        var translatedStringTable = baseLanguageStrings.Select(s =>
        {
            // Copy the entry, but mark the new language
            return(new StringTableEntry(s)
            {
                Language = language
            });
        });

        // Convert this new list to a CSV
        string generatedCSV;

        try
        {
            generatedCSV = StringTableEntry.CreateCSV(translatedStringTable);
        }
        catch (CsvHelper.CsvHelperException e)
        {
            Debug.LogError($"Error creating {language} CSV: {e}");
            return;
        }

        // Write out this CSV to a file
        var path            = AssetDatabase.GetAssetPath(serializedObject.targetObject);
        var directory       = Path.GetDirectoryName(path);
        var csvFileName     = $"{Path.GetFileNameWithoutExtension(path)} ({language}).csv";
        var destinationPath = Path.Combine(directory, csvFileName);

        destinationPath = AssetDatabase.GenerateUniqueAssetPath(destinationPath);
        File.WriteAllText(destinationPath, generatedCSV);

        // Import this file as a TextAsset object
        AssetDatabase.ImportAsset(destinationPath);
        var newTextAsset = AssetDatabase.LoadAssetAtPath <TextAsset>(destinationPath);

        // Store this TextAsset in the importer, at the end of the
        // localizations array
        ArrayUtility.Add(ref (serializedObject.targetObject as YarnImporter).localizations, new YarnProgram.YarnTranslation(language, newTextAsset));

        // Mark that we just changed the target object
        EditorUtility.SetDirty(serializedObject.targetObject);
    }
Example #2
0
        /// <summary>
        /// Writes a .csv file to disk at the path indicated by <paramref
        /// name="destination"/>, containing all of the lines found in the
        /// scripts referred to by <paramref name="yarnProjectImporter"/>.
        /// </summary>
        /// <remarks>
        /// The file generated is in a format ready to be added to the <see
        /// cref="YarnProjectImporter.languagesToSourceAssets"/> list.
        /// </remarks>
        /// <param name="yarnProjectImporter">The YarnProjectImporter to
        /// extract strings from.</param>
        /// <param name="destination">The path to write the file
        /// to.</param>
        /// <returns><see langword="true"/> if the file was written
        /// successfully, <see langword="false"/> otherwise.</returns>
        /// <exception cref="CsvHelper.CsvHelperException">Thrown when an
        /// error is encountered when generating the CSV data.</exception>
        /// <exception cref="IOException">Thrown when an error is
        /// encountered when writing the data to disk.</exception>
        internal static bool WriteStringsFile(string destination, YarnProjectImporter yarnProjectImporter)
        {
            // Perform a strings-only compilation to get a full strings
            // table, and generate the CSV.
            var stringTable = yarnProjectImporter.GenerateStringsTable();

            // If there was an error, bail out here
            if (stringTable == null)
            {
                return(false);
            }

            // Convert the string tables to CSV...
            var outputCSV = StringTableEntry.CreateCSV(stringTable);

            // ...and write it to disk.
            File.WriteAllText(destination, outputCSV);

            return(true);
        }
Example #3
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);
    }