2025-05-01 01:48:08 -07:00

794 lines
36 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor.Localization.Plugins.XLIFF.Common;
using UnityEditor.Localization.Reporting;
using UnityEngine.Localization;
using UnityEngine.Localization.Metadata;
using UnityEngine.Localization.Tables;
using UnityEngine.Pool;
namespace UnityEditor.Localization.Plugins.XLIFF
{
/// <summary>
/// XML Localisation Interchange File Format.
/// The purpose of XLIFF is to store localizable data and carry it from one step of the localization process to the other,
/// while allowing interoperability between and among tools.
/// </summary>
public static class Xliff
{
/// <summary>
/// Determines how notes should be handled when importing XLIFF data.
/// </summary>
public enum ImportNotesBehavior
{
/// <summary>
/// Does nothing with the notes and comments.
/// </summary>
Ignore,
/// <summary>
/// Default behavior. Replaces all comments with notes.
/// </summary>
Replace,
/// <summary>
/// Attempts to merge the notes with existing comments by checking the comment contents.
/// Comments that have the same text as a note will be ignored.
/// </summary>
Merge,
}
/// <summary>
/// Optional import options which can be used to configure the importing behavior.
/// </summary>
public class ImportOptions
{
/// <summary>
/// Should the source language tables be updated using <see cref="ITranslationUnit.Source"/>?
/// </summary>
public bool UpdateSourceTable { get; set; } = true;
/// <summary>
/// Should the target language tables be updated using <see cref="ITranslationUnit.Target"/>?
/// </summary>
public bool UpdateTargetTable { get; set; } = true;
/// <summary>
/// Where to create new <see cref="StringTableCollection"/>'s when importing and a matching collection could not be found.
/// If empty a save prompt will be shown.
/// </summary>
public string NewCollectionDirectory { get; set; }
/// <summary>
/// Controls how notes will be imported.
/// </summary>
public ImportNotesBehavior ImportNotes { get; set; } = ImportNotesBehavior.Replace;
}
static readonly ImportOptions k_DefaultOptions = new ImportOptions();
/// <summary>
/// Exports all <see cref="StringTable"/> in <paramref name="collections"/> as 1 or more XLIFF files where each file represents a single language.
/// </summary>
/// <param name="source">This is the language that will be used as the source language for all generated XLIFF files.</param>
/// <param name="directory">The directory to output the generated XLIFF files.</param>
/// <param name="name">The default name for all generated XLIFF files. Files will be saved with the full name "[name]_[Language Code].xlf"</param>
/// <param name="version">The XLIFF version to generate the files in.</param>
/// <param name="collections">1 or more <see cref="StringTableCollection"/>. The collections will be combines into language groups where each file represents a single </param>
/// <param name="reporter">Optional reporter which can report the current progress.</param>
public static void Export(LocaleIdentifier source, string directory, string name, XliffVersion version, ICollection<StringTableCollection> collections, ITaskReporter reporter = null)
{
if (collections == null)
throw new ArgumentNullException(nameof(collections));
var dict = new Dictionary<StringTableCollection, HashSet<int>>();
foreach (var c in collections)
{
dict[c] = new HashSet<int>(Enumerable.Range(0, c.StringTables.Count));
}
ExportSelected(source, directory, name, version, dict, reporter);
}
/// <summary>
/// Export the values in <paramref name="tables"/> using <paramref name="sourceLanguage"/> as the source language to one or more XLIFF files.
/// </summary>
/// <param name="sourceLanguage">This is the table that will be used as the source language for all generated XLIFF files.</param>
/// <param name="directory">The directory where all generated XLIFF files will be saved to.</param>
/// <param name="version">The XLIFF version to generate the files in.</param>
/// <param name="tables">1 or more <see cref="StringTable"/> that will be used as the target language for each XLIFF file. 1 XLIFF file will be generated for each table.</param>
/// <param name="reporter">Optional reporter which can report the current progress.</param>
public static void Export(StringTable sourceLanguage, string directory, XliffVersion version, ICollection<StringTable> tables, ITaskReporter reporter = null)
{
if (sourceLanguage == null)
throw new ArgumentNullException(nameof(sourceLanguage));
if (tables == null)
throw new ArgumentNullException(nameof(tables));
try
{
// Used for reporting
float taskStep = 1.0f / (tables.Count * 2.0f);
float progress = 0;
if (reporter != null && reporter.Started != true)
reporter.Start($"Exporting {tables.Count} String Tables to XLIFF", string.Empty);
// We need the key, source value and translated value.
foreach (var stringTable in tables)
{
reporter?.ReportProgress($"Exporting {stringTable.name}", progress);
progress += taskStep;
var doc = CreateDocument(sourceLanguage.LocaleIdentifier, stringTable.LocaleIdentifier, version);
AddTableToDocument(doc, sourceLanguage, stringTable);
var cleanName = CleanFileName(stringTable.name);
var fileName = $"{cleanName}.xlf";
var filePath = Path.Combine(directory, fileName);
using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
doc.Serialize(stream);
}
}
reporter?.Completed($"Finished exporting");
}
catch (Exception e)
{
reporter?.Fail(e.Message);
throw;
}
}
/// <summary>
/// Creates an empty XLIFF document ready for populating.
/// </summary>
/// <param name="source">The source language. The language used when populating <see cref="ITranslationUnit.Source"/>.</param>
/// <param name="target">The target language. The language used when populating <see cref="ITranslationUnit.Target"/>.</param>
/// <param name="version">The XLIFF file version.</param>
/// <returns></returns>
public static IXliffDocument CreateDocument(LocaleIdentifier source, LocaleIdentifier target, XliffVersion version)
{
var doc = XliffDocument.Create(version);
doc.SourceLanguage = source.Code;
doc.TargetLanguage = target.Code;
return doc;
}
/// <summary>
/// Populate the document with the entries from <paramref name="target"/> using <paramref name="source"/> as the source reference.
/// Note: The source and target tables must be part of the same collection, they must both use the same <see cref="SharedTableData"/>.
/// </summary>
/// <param name="document">The XLIFF document to add the entries to.</param>
/// <param name="source"></param>
/// <param name="target"></param>
public static void AddTableToDocument(IXliffDocument document, StringTable source, StringTable target)
{
if (document == null)
throw new ArgumentNullException(nameof(document));
if (source == null)
throw new ArgumentNullException(nameof(source));
if (target == null)
throw new ArgumentNullException(nameof(target));
if (source.SharedData != target.SharedData)
throw new Exception("Source and Target StringTables must be part of the same collection and use the same SharedTableData.");
var file = document.AddNewFile();
var filePath = AssetDatabase.GetAssetPath(target);
file.Original = filePath;
file.Id = AssetDatabase.AssetPathToGUID(filePath);
var group = file.AddNewGroup();
group.Id = TableReference.StringFromGuid(target.SharedData.TableCollectionNameGuid);
group.Name = target.SharedData.TableCollectionName;
AddNotesFromMetadata(group, target.SharedData.Metadata, NoteType.General);
AddNotesFromMetadata(group, source, NoteType.Source);
if (source != target)
AddNotesFromMetadata(group, target, NoteType.Target);
foreach (var row in StringTableCollection.GetRowEnumerator(source, target))
{
if (row.TableEntries[0] != null && row.TableEntries[0].SharedEntry.Metadata.HasMetadata<ExcludeEntryFromExport>())
continue;
var unit = group.AddNewTranslationUnit();
unit.Id = row.KeyEntry.Id.ToString();
unit.Name = row.KeyEntry.Key;
unit.Source = row.TableEntries[0]?.Value;
// Dont add a value if its empty.
if (row.TableEntries[1] != null && !string.IsNullOrEmpty(row.TableEntries[1].Value) && !string.IsNullOrEmpty(row.TableEntries[1].Key))
unit.Target = row.TableEntries[1].Value;
// Add notes
AddNotesFromMetadata(unit, row.KeyEntry.Metadata, NoteType.General);
AddNotesFromMetadata(unit, row.TableEntries[0], NoteType.Source);
if (source != target)
AddNotesFromMetadata(unit, row.TableEntries[1], NoteType.Target);
}
}
static void AddNotesFromMetadata(INoteCollection noteCollection, IMetadataCollection metadata, NoteType noteType)
{
// May be null if the entry is missing for the current row
if (metadata == null)
return;
using (ListPool<Comment>.Get(out var comments))
{
metadata.GetMetadatas<Comment>(comments);
foreach (var com in comments)
{
var note = noteCollection.AddNewNote();
note.AppliesTo = noteType;
note.NoteText = com.CommentText;
}
}
}
/// <summary>
/// Imports all XLIFF files with the extensions xlf or xliff into existing <see cref="StringTableCollection"/> or new ones if a matching one could not be found.
/// </summary>
/// <param name="directory">The directory to search. Searches sub directories as well.</param>
/// <param name="importOptions">Optional import options which can be used to configure the importing behavior.</param>
/// <param name="reporter">Optional reporter which can report the current progress.</param>
public static void ImportDirectory(string directory, ImportOptions importOptions = null, ITaskReporter reporter = null)
{
try
{
if (reporter != null && reporter.Started != true)
reporter.Start("Importing XLIFF files in directory", "Finding xlf and xliff files.");
var files = Directory.GetFiles(directory, "*", SearchOption.AllDirectories);
var filteredFiles = files.Where(s => s.EndsWith(".xlf") || s.EndsWith(".xliff"));
float taskStep = filteredFiles.Count() / 1.0f;
float progress = taskStep;
foreach (var f in filteredFiles)
{
reporter?.ReportProgress($"Importing {f}", progress);
progress += taskStep;
// Don't pass the reporter in as it will be Completed after each file and we only want to do that at the end.
ImportFile(f, importOptions);
}
reporter?.Completed("Finished importing XLIFF files");
}
catch (Exception e)
{
reporter?.Fail(e.Message);
throw;
}
}
/// <summary>
/// Imports a single XLIFF file into the project.
/// Attempts to find matching <see cref="StringTableCollection"/>'s, if one could not be found then a new one is created.
/// </summary>
/// <param name="file">The XLIFF file.</param>
/// <param name="importOptions">Optional import options which can be used to configure the importing behavior.</param>
/// <param name="reporter">Optional reporter which can report the current progress.</param>
public static void ImportFile(string file, ImportOptions importOptions = null, ITaskReporter reporter = null)
{
if (reporter != null && reporter.Started != true)
reporter.Start("Importing XLIFF", $"Importing {file}");
try
{
if (!File.Exists(file))
throw new FileNotFoundException($"Could not find file {file}");
using (var stream = new FileStream(file, FileMode.Open, FileAccess.Read))
{
reporter?.ReportProgress("Parsing XLIFF", 0.1f);
var document = XliffDocument.Parse(stream);
ImportDocument(document, importOptions, reporter);
}
}
catch (Exception e)
{
reporter?.Fail(e.Message);
throw;
}
}
/// <summary>
/// Imports a single XLIFF document into the project.
/// Attempts to find matching <see cref="StringTableCollection"/>'s, if one could not be found then a new one is created.
/// </summary>
/// <param name="document">The root XLIFF document.</param>
/// <param name="importOptions">Optional import options which can be used to configure the importing behavior.</param>
/// <param name="reporter">Optional reporter which can report the current progress.</param>
public static void ImportDocument(IXliffDocument document, ImportOptions importOptions = null, ITaskReporter reporter = null)
{
if (document == null)
throw new ArgumentNullException(nameof(document));
if (reporter != null && reporter.Started != true)
reporter.Start("Importing XLIFF", "Importing document");
try
{
float progress = reporter == null ? 0.1f : reporter.CurrentProgress + 0.1f;
reporter?.ReportProgress("Importing XLIFF into project", progress);
float progressStep = document.FileCount / (1.0f - progress);
var options = importOptions ?? k_DefaultOptions;
for (int i = 0; i < document.FileCount; ++i)
{
var f = document.GetFile(i);
progress += progressStep;
reporter?.ReportProgress($"Importing({i + 1}/{document.FileCount}) {f.Id}", progress);
ImportFileNode(f, document.SourceLanguage, document.TargetLanguage, options);
}
reporter?.Completed("Finished importing XLIFF");
}
catch (Exception e)
{
reporter?.Fail(e.Message);
throw;
}
}
static void ImportFileNode(IFile file, LocaleIdentifier source, LocaleIdentifier target, ImportOptions importOptions)
{
// Find the string table and collection for this file
var collection = FindProjectCollection(file);
// Import translation units which have no groups.
INoteCollection extraNodes = file;
if (file.TranslationUnitCount > 0)
{
if (collection == null)
{
var dir = importOptions.NewCollectionDirectory;
if (string.IsNullOrEmpty(dir))
dir = EditorUtility.SaveFolderPanel($"Create new String Table Collection {file.Id}", "Assets", file.Id);
if (!string.IsNullOrEmpty(dir))
{
var newCollection = LocalizationEditorSettings.CreateStringTableCollection(file.Id, dir);
extraNodes = null;
ImportFileIntoCollection(newCollection, file, source, target, importOptions);
}
}
else
{
extraNodes = null;
ImportFileIntoCollection(collection, file, source, target, importOptions);
collection.SaveChangesToDisk();
}
}
for (int i = 0; i < file.GroupCount; ++i)
{
var group = file.GetGroup(i);
var groupCollection = FindProjectCollection(group) ?? collection;
if (groupCollection == null)
{
// Use the provided directory otherwise ask the user to provide one
var dir = importOptions.NewCollectionDirectory;
if (string.IsNullOrEmpty(dir))
{
dir = EditorUtility.SaveFolderPanel($"Create new String Table Collection {file.Id}", "Assets", file.Id);
if (string.IsNullOrEmpty(dir))
continue;
}
var collectionName = string.IsNullOrEmpty(group.Name) ? group.Id : group.Name;
groupCollection = LocalizationEditorSettings.CreateStringTableCollection(collectionName, dir);
}
ImportGroupIntoCollection(groupCollection, group, extraNodes, source, target, importOptions);
groupCollection.SaveChangesToDisk();
}
}
static StringTableCollection FindProjectCollection(IFile file)
{
// When exporting we use the ID for the file GUID.
string path = AssetDatabase.GUIDToAssetPath(file.Id);
var filePath = string.IsNullOrEmpty(path) ? file.Original : path;
if (!string.IsNullOrEmpty(filePath))
{
var table = AssetDatabase.LoadAssetAtPath<StringTable>(filePath);
if (table != null)
return LocalizationEditorSettings.GetCollectionFromTable(table) as StringTableCollection;
}
return null;
}
static StringTableCollection FindProjectCollection(IGroup group)
{
// Is the Id the Shared table data GUID?
if (!string.IsNullOrEmpty(group.Id))
{
var path = AssetDatabase.GUIDToAssetPath(group.Id);
if (!string.IsNullOrEmpty(path))
{
var sharedTableData = AssetDatabase.LoadAssetAtPath<SharedTableData>(path);
if (sharedTableData != null)
return LocalizationEditorSettings.GetCollectionForSharedTableData(sharedTableData) as StringTableCollection;
}
}
// Try table name instead
return LocalizationEditorSettings.GetStringTableCollection(group.Id) ?? LocalizationEditorSettings.GetStringTableCollection(group.Name);
}
/// <summary>
/// Import the XLIFF file into the collection.
/// </summary>
/// <param name="collection">The collection to import all the XLIFF data into.</param>
/// <param name="file">The XLIFF file path.</param>
/// <param name="importOptions">Optional import options which can be used to configure the importing behavior.</param>
/// <param name="reporter">Optional reporter which can report the current progress.</param>
public static void ImportFileIntoCollection(StringTableCollection collection, string file, ImportOptions importOptions = null, ITaskReporter reporter = null)
{
if (collection == null)
throw new ArgumentNullException(nameof(collection));
if (reporter != null && reporter.Started != true)
reporter.Start("Importing XLIFF", $"Importing {file}");
try
{
if (!File.Exists(file))
throw new FileNotFoundException($"Could not find file {file}");
using (var stream = new FileStream(file, FileMode.Open, FileAccess.Read))
{
reporter?.ReportProgress("Parsing XLIFF", 0.1f);
var document = XliffDocument.Parse(stream);
float progress = 0.3f;
reporter?.ReportProgress("Importing XLIFF into project", progress);
float progressStep = document.FileCount / 1.0f * 0.7f;
var options = importOptions ?? k_DefaultOptions;
for (int i = 0; i < document.FileCount; ++i)
{
var f = document.GetFile(i);
progress += progressStep;
reporter?.ReportProgress($"Importing({i + 1}/{document.FileCount}) {f.Id}", progress);
ImportFileIntoCollection(collection, f, document.SourceLanguage, document.TargetLanguage, options);
}
reporter?.Completed("Finished importing XLIFF");
}
}
catch (Exception e)
{
reporter?.Fail(e.Message);
throw;
}
}
static void ImportFileIntoCollection(StringTableCollection collection, IFile file, LocaleIdentifier source, LocaleIdentifier target, ImportOptions importOptions)
{
if (collection == null)
throw new ArgumentNullException(nameof(collection));
var sourceTable = collection.GetTable(source) ?? collection.AddNewTable(source);
var targetTable = collection.GetTable(target) ?? collection.AddNewTable(target);
// Extract file comments?
AddMetadataCommentsFromNotes(file, collection.SharedData.Metadata, NoteType.General, importOptions.ImportNotes);
AddMetadataCommentsFromNotes(file, sourceTable, NoteType.Source, importOptions.ImportNotes);
if (sourceTable != targetTable)
AddMetadataCommentsFromNotes(file, targetTable, NoteType.Target, importOptions.ImportNotes);
ImportIntoTables(file, sourceTable as StringTable, targetTable as StringTable, importOptions);
LocalizationEditorSettings.EditorEvents.RaiseCollectionModified(null, collection);
}
static void ImportGroupIntoCollection(StringTableCollection collection, IGroup group, INoteCollection extraNotes, LocaleIdentifier source, LocaleIdentifier target, ImportOptions importOptions)
{
if (collection == null)
throw new ArgumentNullException(nameof(collection));
var sourceTable = collection.GetTable(source) ?? collection.AddNewTable(source);
var targetTable = collection.GetTable(target) ?? collection.AddNewTable(target);
// Extract file comments?
var generalNotes = AddMetadataCommentsFromNotes(group, collection.SharedData.Metadata, NoteType.General, importOptions.ImportNotes);
var sourceNotes = AddMetadataCommentsFromNotes(group, sourceTable, NoteType.Source, importOptions.ImportNotes);
int targetNotes = sourceTable != targetTable ? AddMetadataCommentsFromNotes(group, targetTable, NoteType.Target, importOptions.ImportNotes) : 0;
// If we are importing a group and the file contains notes that were not used then we can include them as extras here.
if (extraNotes != null)
{
// If we imported some notes from the group then we need to switch to merge or we will lose those notes.
var overrideBehavior = generalNotes > 0 ? ImportNotesBehavior.Merge : importOptions.ImportNotes;
AddMetadataCommentsFromNotes(extraNotes, collection.SharedData.Metadata, NoteType.General, overrideBehavior);
overrideBehavior = sourceNotes > 0 ? ImportNotesBehavior.Merge : importOptions.ImportNotes;
AddMetadataCommentsFromNotes(extraNotes, sourceTable, NoteType.Source, overrideBehavior);
overrideBehavior = targetNotes > 0 ? ImportNotesBehavior.Merge : importOptions.ImportNotes;
if (sourceTable != targetTable)
AddMetadataCommentsFromNotes(extraNotes, targetTable, NoteType.Target, overrideBehavior);
}
ImportIntoTables(group, sourceTable as StringTable, targetTable as StringTable, importOptions);
LocalizationEditorSettings.EditorEvents.RaiseCollectionModified(null, collection);
}
static void ImportIntoTables(ITranslationUnitCollection unitCollection, StringTable source, StringTable target, ImportOptions importOptions = null)
{
var options = importOptions ?? k_DefaultOptions;
var sharedTableData = target.SharedData;
EditorUtility.SetDirty(sharedTableData);
if (importOptions.UpdateSourceTable)
EditorUtility.SetDirty(source);
if (importOptions.UpdateTargetTable)
EditorUtility.SetDirty(target);
for (int i = 0; i < unitCollection.TranslationUnitCount; ++i)
{
var tu = unitCollection.GetTranslationUnit(i);
var sharedTableEntry = GetOrCreateEntryFromTranslationUnit(sharedTableData, tu);
AddMetadataCommentsFromNotes(tu, sharedTableEntry.Metadata, NoteType.General, options.ImportNotes);
if (options.UpdateSourceTable)
{
var sourceEntry = source.AddEntry(sharedTableEntry.Id, tu.Source);
AddMetadataCommentsFromNotes(tu, sourceEntry, NoteType.Source, options.ImportNotes);
}
if (options.UpdateTargetTable)
{
var targetEntry = target.AddEntry(sharedTableEntry.Id, tu.Target);
AddMetadataCommentsFromNotes(tu, targetEntry, NoteType.Target, options.ImportNotes);
}
}
// Nested groups
if (unitCollection is IGroupCollection groupCollection)
{
for (int i = 0; i < groupCollection.GroupCount; ++i)
{
var group = groupCollection.GetGroup(i);
ImportIntoTables(group, source, target, options);
}
}
}
/// <summary>
/// Import an XLIFF file into the target table, ignoring <see cref="IXliffDocument.TargetLanguage"/>.
/// </summary>
/// <param name="file">The XLIFF file path.</param>
/// <param name="target">The target table that will be populated with the translated values.</param>
/// <param name="importNotesBehavior">How should the notes be imported?</param>
/// <param name="reporter">Optional reporter which can report the current progress.</param>
public static void ImportFileIntoTable(string file, StringTable target, ImportNotesBehavior importNotesBehavior = ImportNotesBehavior.Replace, ITaskReporter reporter = null)
{
if (target == null)
throw new ArgumentNullException(nameof(target));
if (reporter != null && reporter.Started != true)
reporter.Start("Importing XLIFF", $"Importing {file}");
try
{
if (!File.Exists(file))
throw new FileNotFoundException($"Could not find file {file}");
using (var stream = new FileStream(file, FileMode.Open, FileAccess.Read))
{
reporter?.ReportProgress("Parsing XLIFF", 0.1f);
var document = XliffDocument.Parse(stream);
ImportDocumentIntoTable(document, target, importNotesBehavior, reporter);
}
}
catch (Exception e)
{
reporter?.Fail(e.Message);
throw;
}
}
/// <summary>
/// Import an XLIFF document into the target table, ignoring <see cref="IXliffDocument.TargetLanguage"/>.
/// </summary>
/// <param name="document">The XLIFF document to import.</param>
/// <param name="target">The target table that will be populated with the translated values.</param>
/// <param name="importNotesBehavior">How should the notes be imported?</param>
/// <param name="reporter">Optional reporter which can report the current progress.</param>
public static void ImportDocumentIntoTable(IXliffDocument document, StringTable target, ImportNotesBehavior importNotesBehavior = ImportNotesBehavior.Replace, ITaskReporter reporter = null)
{
if (document == null)
throw new ArgumentNullException(nameof(document));
if (target == null)
throw new ArgumentNullException(nameof(target));
EditorUtility.SetDirty(target);
float progress = reporter == null ? 0 : reporter.CurrentProgress + 0.1f;
reporter?.ReportProgress("Importing XLIFF into table", progress);
float progressStep = document.FileCount / (1.0f - progress);
var options = new ImportOptions { UpdateSourceTable = false, ImportNotes = importNotesBehavior };
for (int i = 0; i < document.FileCount; ++i)
{
var f = document.GetFile(i);
progress += progressStep;
reporter?.ReportProgress($"Importing({i + 1}/{document.FileCount}) {f.Id}", progress);
ImportIntoTables(f, null, target, options);
}
var collection = LocalizationEditorSettings.GetCollectionFromTable(target);
if (collection != null)
{
LocalizationEditorSettings.EditorEvents.RaiseCollectionModified(document, collection);
collection.SaveChangesToDisk();
}
reporter?.Completed("Finished importing XLIFF");
}
static SharedTableData.SharedTableEntry GetOrCreateEntryFromTranslationUnit(SharedTableData sharedTableData, ITranslationUnit unit)
{
// Does it contain an id?
long keyId = SharedTableData.EmptyId;
string name = null;
if (!string.IsNullOrEmpty(unit.Id))
{
if (long.TryParse(unit.Id, out keyId))
{
var entry = sharedTableData.GetEntry(keyId);
if (entry != null)
return entry;
}
else
{
// Is the Id a name?
var entry = sharedTableData.GetEntry(unit.Id);
if (entry != null)
return entry;
name = unit.Id;
}
}
// Use the name
if (!string.IsNullOrEmpty(unit.Name))
{
var entry = sharedTableData.GetEntry(unit.Name);
if (entry != null)
return entry;
name = unit.Name;
}
// Create a new entry
if (keyId != SharedTableData.EmptyId)
return sharedTableData.AddKey(name, keyId);
return sharedTableData.AddKey(name);
}
static int AddMetadataCommentsFromNotes(INoteCollection notes, IMetadataCollection metadata, NoteType requiredNoteType, ImportNotesBehavior importNotes)
{
if (importNotes == ImportNotesBehavior.Ignore)
return 0;
int count = 0;
using (ListPool<Comment>.Get(out var comments))
{
metadata.GetMetadatas<Comment>(comments);
if (importNotes == ImportNotesBehavior.Replace)
{
foreach (var com in comments)
{
metadata.RemoveMetadata(com);
}
comments.Clear();
}
for (int i = 0; i < notes.NoteCount; ++i)
{
var n = notes.GetNote(i);
if (n.AppliesTo != requiredNoteType)
continue;
if (importNotes == ImportNotesBehavior.Merge)
{
// See if the note already exists
if (comments.Any(c => c.CommentText == n.NoteText))
continue;
}
// Add a new note
metadata.AddMetadata(new Comment { CommentText = n.NoteText });
count++;
}
}
return count;
}
internal static void ExportSelected(LocaleIdentifier source, string dir, string name, XliffVersion version, Dictionary<StringTableCollection, HashSet<int>> collectionsWithSelectedIndexes, ITaskReporter reporter = null)
{
var documents = DictionaryPool<LocaleIdentifier, IXliffDocument>.Get();
try
{
// Used for reporting
int totalTasks = collectionsWithSelectedIndexes.Sum(c => c.Value.Count);
float taskStep = 1.0f / (totalTasks * 2.0f);
float progress = 0;
if (reporter != null && reporter.Started != true)
reporter.Start($"Exporting {totalTasks} String Tables to XLIFF", string.Empty);
foreach (var kvp in collectionsWithSelectedIndexes)
{
var stringTableCollection = kvp.Key;
var sourceTable = stringTableCollection.GetTable(source) as StringTable;
if (sourceTable == null)
{
var message = $"Collection {stringTableCollection.TableCollectionName} does not contain a table for the source language {source}";
reporter?.Fail(message);
throw new Exception(message);
}
foreach (var stringTableIndex in kvp.Value)
{
var stringTable = stringTableCollection.StringTables[stringTableIndex];
reporter?.ReportProgress($"Generating document for {stringTable.name}", progress);
progress += taskStep;
if (!documents.TryGetValue(stringTable.LocaleIdentifier, out var targetDoc))
{
targetDoc = CreateDocument(source, stringTable.LocaleIdentifier, version);
documents[stringTable.LocaleIdentifier] = targetDoc;
}
AddTableToDocument(targetDoc, sourceTable, stringTable);
}
}
// Now write the files
foreach (var doc in documents)
{
var cleanName = CleanFileName(name);
var fileName = $"{cleanName}_{doc.Key.Code}.xlf";
var filePath = Path.Combine(dir, fileName);
reporter?.ReportProgress($"Writing {fileName}", progress);
progress += taskStep;
using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
doc.Value.Serialize(stream);
}
}
reporter?.Completed($"Finished exporting");
}
catch (Exception e)
{
reporter?.Fail(e.Message);
throw;
}
finally
{
DictionaryPool<LocaleIdentifier, IXliffDocument>.Release(documents);
}
}
static string CleanFileName(string fileName)
{
// Removes invalid characters from the filename
return Path.GetInvalidFileNameChars().Aggregate(fileName, (current, c) => current.Replace(c.ToString(), "_"));
}
}
}