/// <summary> /// May only be called from constructor. Not thread-safe. /// </summary> private void MergeXliffFilesIntoCache(IEnumerable <string> xliffFiles) { DefaultXliffDocument = XLiffDocument.Read(OwningManager.DefaultStringFilePath); // read the generated file // It's possible (I think when there is no customizable Xliff, as on first install, but the version in the installed Xliff // is out of date with the app) that we don't have all the info from the installed Xliff in the customizable one. // We want to make sure that (a) any new dynamic strings in the installed one are considered valid by default // (b) any newly obsolete IDs are noted. if (File.Exists(OwningManager.DefaultInstalledStringFilePath)) { if (!XLiffLocalizationManager.ScanningForCurrentStrings) { var defaultInstalledXliffDoc = XLiffDocument.Read(OwningManager.DefaultInstalledStringFilePath); foreach (var tu in defaultInstalledXliffDoc.File.Body.TransUnitsUnordered) { DefaultXliffDocument.File.Body.AddTransUnitOrVariantFromExisting(tu, LocalizationManager.kDefaultLang); } } } XliffDocuments.TryAdd(LocalizationManager.kDefaultLang, DefaultXliffDocument); // Map the default language onto itself. LocalizationManagerInternal <XLiffDocument> .MapToExistingLanguage[LocalizationManager.kDefaultLang] = LocalizationManager.kDefaultLang; foreach (var file in xliffFiles) { var langId = XLiffLocalizationManager.GetLangIdFromXliffFileName(file); Debug.Assert(!string.IsNullOrEmpty(langId)); Debug.Assert(langId != LocalizationManager.kDefaultLang); _unloadedXliffDocuments[langId] = file; } }
public void MergeXliffDocuments_WorksAsExpected() { var oldDoc = CreateTestDocument(); var newDoc = CreateTestDocument(); AdjustDocumentForTestingMerge(newDoc); var mergedDoc = XLiffLocalizationManager.MergeXliffDocuments(newDoc, oldDoc, true); Assert.IsNotNull(mergedDoc); Assert.That(5, Is.EqualTo(oldDoc.File.Body.TransUnits.Count)); Assert.That(6, Is.EqualTo(newDoc.File.Body.TransUnits.Count)); Assert.That(8, Is.EqualTo(mergedDoc.File.Body.TransUnits.Count)); var tu = mergedDoc.GetTransUnitForId("This.test"); CheckMergedTransUnit(tu, "This is a test.", new[] { "ID: This.test", "This is only a test" }, false); tu = mergedDoc.GetTransUnitForId("That.test"); CheckMergedTransUnit(tu, "That was a test.", new[] { "ID: That.test", "That is hard to explain, but a literal rendition is okay.", "Not found in static scan of compiled code (version 3.1.4)" }, false); tu = mergedDoc.GetTransUnitForId("What.test"); CheckMergedTransUnit(tu, "What is a good test?", new[] { "ID: What.test", "[OLD NOTE] Whatever you say...", "[OLD NOTE] This should be a question.", "OLD TEXT (before 1.0): What are good test.", "OLD TEXT (before 3.1.4): What is good test." }, true); tu = mergedDoc.GetTransUnitForId("What.this"); CheckMergedTransUnit(tu, "What is this nonsense?", new[] { "ID: What.this", "Not dynamic: found in static scan of compiled code (version 3.1.4)" }, false); Assert.IsNotNull(tu); tu = mergedDoc.GetTransUnitForId("This.new1"); CheckMergedTransUnit(tu, "This is a new test.", new[] { "ID: This.new1", "This is still only a test" }, false); tu = mergedDoc.GetTransUnitForId("That.new2"); CheckMergedTransUnit(tu, "That was an old test.", new[] { "ID: That.new2", "That should be easy to translate." }, false); tu = mergedDoc.GetTransUnitForId("What.new3"); CheckMergedTransUnit(tu, "What's up, doc?", new[] { "ID: What.new3" }, true); tu = mergedDoc.GetTransUnitForId("How.now"); CheckMergedTransUnit(tu, "How now brown cow", new[] { "ID: How.now", "Not found when running compiled program (version 3.1.4)" }, true); }
static void Main(string[] args) { // Parse and check the command line arguments. if (!ParseOptions(args)) { Usage(); return; } LocalizationManager.TranslationMemoryKind = TranslationMemory.XLiff; // Load the input assemblies so that they can be scanned. List <Assembly> assemblies = new List <Assembly>(); foreach (var file in _assemblyFiles) { var asm = Assembly.LoadFile(file); if (asm != null) { assemblies.Add(asm); } } // Scan the input assemblies for localizable strings. var extractor = new StringExtractor <XLiffDocument> { ExternalAssembliesToScan = assemblies.ToArray() }; var localizedStrings = extractor.DoExtractingWork(_namespaces.ToArray(), null); // The arguments to this constructor don't really matter much as they're used internally by // L10NSharp for reasons that may not percolate out to xliff. We just need a LocalizationManagerInternal // to feed into the constructor the LocalizedStringCache that does some heavy lifting for us in // creating the XliffDocument from the newly extracted localized strings. var lm = new XLiffLocalizationManager(_fileOriginal, _fileOriginal, _fileProductVersion); var stringCache = new XLiffLocalizedStringCache(lm, false); foreach (var locInfo in localizedStrings) { stringCache.UpdateLocalizedInfo(locInfo); } // Get the newly loaded static strings (in newDoc) and the baseline XLIFF (in baseDoc). var newDoc = stringCache.XliffDocuments[kDefaultLangId]; var baseDoc = LoadBaselineAndCompare(newDoc); // Save the results to the output file, merging in data from the baseline XLIFF if one was specified. var xliffOutput = XLiffLocalizationManager.MergeXliffDocuments(newDoc, baseDoc, _verbose); xliffOutput.File.SourceLang = kDefaultLangId; xliffOutput.File.ProductVersion = !string.IsNullOrEmpty(_fileProductVersion) ? _fileProductVersion : newDoc.File.ProductVersion; xliffOutput.File.HardLineBreakReplacement = kDefaultNewlineReplacement; xliffOutput.File.AmpersandReplacement = kDefaultAmpersandReplacement; xliffOutput.File.Original = _fileOriginal; xliffOutput.File.DataType = !string.IsNullOrEmpty(_fileDatatype) ? _fileDatatype : newDoc.File.DataType; xliffOutput.Save(_xliffOutputFilename); }
/// ------------------------------------------------------------------------------------ /// <summary> /// Now that L10NSharp creates all writable Xliff/Tmx files under LocalApplicationData /// instead of the common/shared AppData folder, applications can use this method to /// purge old Xliff/Tmx files.</summary> /// <param name="appId">ID of the application used for creating the Xliff/Tmx files /// (typically the same ID passed as the 2nd parameter to LocalizationManagerInternal.Create). /// </param> /// <param name="directoryOfWritableTranslationFiles">Folder from which to delete /// Xliff/Tmx files.</param> /// <param name="directoryOfInstalledTranslationFiles">Used to limit file deletion to only /// include copies of the installed Xliff/Tmx files (plus the generated default file). If /// this is <c>null</c>, then all Xliff/Tmx files for the given appID will be deleted from /// <paramref name="directoryOfWritableTranslationFiles"/></param> /// ------------------------------------------------------------------------------------ public static void DeleteOldTranslationFiles(string appId, string directoryOfWritableTranslationFiles, string directoryOfInstalledTranslationFiles) { switch (TranslationMemoryKind) { case TranslationMemory.XLiff: XLiffLocalizationManager.DeleteOldXliffFiles(appId, directoryOfWritableTranslationFiles, directoryOfInstalledTranslationFiles); break; default: TMXLocalizationManager.DeleteOldTmxFiles(appId, directoryOfWritableTranslationFiles, directoryOfInstalledTranslationFiles); break; } }
static void Main(string[] args) { // Parse and check the command line arguments. if (!ParseOptions(args)) { Usage(); return; } LocalizationManager.TranslationMemoryKind = TranslationMemory.XLiff; // Load the input assemblies so that they can be scanned. List <Assembly> assemblies = new List <Assembly>(); List <string> assemblyPaths = new List <string>(); if (_glob) { foreach (var glob in _assemblyFiles) { assemblyPaths.AddRange(Directory.GetFiles(Path.GetDirectoryName(glob), Path.GetFileName(glob))); } } else { assemblyPaths = _assemblyFiles; } foreach (var file in assemblyPaths) { // Using LoadFrom to make sure we pick up other assemblies in the same directory so we don't fail // to load because of 'missing' dependencies var asm = Assembly.LoadFrom(file); assemblies.Add(asm); for (var index = 0; index < _additionalLocalizationMethodNames.Count; index++) { var methodNameSpec = _additionalLocalizationMethodNames[index]; try { var type = asm.GetType(methodNameSpec.Item1 + "." + methodNameSpec.Item2); if (type == null) { continue; } _additionalLocalizationMethods.AddRange(type .GetMethods(BindingFlags.Static | BindingFlags.Public) .Where(m => m.Name == methodNameSpec.Item3)); if (_verbose) { Console.WriteLine($"Method {methodNameSpec.Item2}.{methodNameSpec.Item3} in {asm.GetName().FullName} will be treated as a localization method."); } _additionalLocalizationMethodNames.RemoveAt(index--); } catch (Exception e) { if (_verbose) { Console.WriteLine("Error using reflection on {asm.GetName().FullName} to get type {methodNameSpec.Item2} or method {methodNameSpec.Item3}:" + e.Message); } } } } if (_verbose && _additionalLocalizationMethodNames.Any()) { Console.WriteLine("Failed to find the following additional localization methods:"); foreach (var methodNameSpec in _additionalLocalizationMethodNames) { Console.WriteLine($"{methodNameSpec.Item1}.{methodNameSpec.Item2}.{methodNameSpec.Item3}"); } } // Scan the input assemblies for localizable strings. var extractor = new StringExtractor <XLiffDocument> { ExternalAssembliesToScan = assemblies.ToArray() }; extractor.OutputErrorsToConsole = _verbose; var localizedStrings = extractor.DoExtractingWork(_additionalLocalizationMethods, _namespaces.ToArray(), null); // The arguments to this constructor don't really matter much as they're used internally by // L10NSharp for reasons that may not percolate out to xliff. We just need a LocalizationManagerInternal // to feed into the constructor the LocalizedStringCache that does some heavy lifting for us in // creating the XliffDocument from the newly extracted localized strings. var lm = new XLiffLocalizationManager(_fileOriginal, _fileOriginal, _fileProductVersion); var stringCache = new XLiffLocalizedStringCache(lm, false); foreach (var locInfo in localizedStrings) { stringCache.UpdateLocalizedInfo(locInfo); } // Get the newly loaded static strings (in newDoc) and the baseline XLIFF (in baseDoc). var newDoc = stringCache.GetDocument(kDefaultLangId); var baseDoc = LoadBaselineAndCompare(newDoc); // Save the results to the output file, merging in data from the baseline XLIFF if one was specified. var xliffOutput = XLiffLocalizationManager.MergeXliffDocuments(newDoc, baseDoc, _verbose); xliffOutput.File.SourceLang = kDefaultLangId; xliffOutput.File.ProductVersion = !string.IsNullOrEmpty(_fileProductVersion) ? _fileProductVersion : newDoc.File.ProductVersion; xliffOutput.File.HardLineBreakReplacement = kDefaultNewlineReplacement; xliffOutput.File.AmpersandReplacement = kDefaultAmpersandReplacement; xliffOutput.File.Original = _fileOriginal; xliffOutput.File.DataType = !string.IsNullOrEmpty(_fileDatatype) ? _fileDatatype : newDoc.File.DataType; xliffOutput.Save(_xliffOutputFilename); }
/// ------------------------------------------------------------------------------------ /// <summary> /// Now that L10NSharp creates all writable Xliff files under LocalApplicationData /// instead of the common/shared AppData folder, applications can use this method to /// purge old Xliff files.</summary> /// <param name="appId">ID of the application used for creating the Xliff files (typically /// the same ID passed as the 2nd parameter to LocalizationManagerInternal.Create).</param> /// <param name="directoryOfWritableXliffFiles">Folder from which to delete Xliff files. /// </param> /// <param name="directoryOfInstalledXliffFiles">Used to limit file deletion to only /// include copies of the installed Xliff files (plus the generated default file). If this /// is <c>null</c>, then all Xliff files for the given appID will be deleted from /// <paramref name="directoryOfWritableXliffFiles"/></param> /// ------------------------------------------------------------------------------------ public static void DeleteOldXliffFiles(string appId, string directoryOfWritableXliffFiles, string directoryOfInstalledXliffFiles) { XLiffLocalizationManager.DeleteOldXliffFiles(appId, directoryOfWritableXliffFiles, directoryOfInstalledXliffFiles); }