/// ------------------------------------------------------------------------------------ /// <summary> /// Saves the cache to the files from which the cache was originally loaded, but only /// if the cache is dirty. If the cache is dirty and saved, then true is returned. /// Otherwise, false is returned. /// </summary> /// ------------------------------------------------------------------------------------ internal void SaveIfDirty(ICollection <string> langIdsToForceCreate) { if (!IsDirty) { return; } StringBuilder errorMsg = null; foreach (var langId in XliffDocuments.Keys) { try { if (XliffDocuments[langId].IsDirty) { SaveFileForLangId(langId, langIdsToForceCreate != null && langIdsToForceCreate.Contains(langId), XliffDocuments[langId]); } } catch (Exception e) { if (e is SecurityException || e is UnauthorizedAccessException || e is IOException) { if (errorMsg == null) { errorMsg = new StringBuilder(); errorMsg.AppendLine("Failed to save localization changes in the following files:"); } errorMsg.AppendLine(); errorMsg.Append("File: "); errorMsg.AppendLine(OwningManager.GetPathForLanguage(langId, true)); errorMsg.Append("Error Type: "); errorMsg.AppendLine(e.GetType().ToString()); errorMsg.Append("Message: "); errorMsg.AppendLine(e.Message); } } } if (errorMsg != null) { throw new IOException(errorMsg.ToString()); } IsDirty = false; }
/// ------------------------------------------------------------------------------------ private void SaveFileForLangId(string langId, bool forceCreation, XLiffDocument xliffOriginal) { if (!forceCreation && !OwningManager.DoesCustomizedTranslationExistForLanguage(langId)) { return; } var xliffOutput = CreateEmptyStringFile(); if (langId != LocalizationManager.kDefaultLang) { xliffOutput.File.TargetLang = langId; } xliffOutput.File.ProductVersion = OwningManager.AppVersion; xliffOutput.File.HardLineBreakReplacement = s_literalNewline; xliffOutput.File.AmpersandReplacement = _ampersandReplacement; if (OwningManager != null && OwningManager.Name != null) { xliffOutput.File.Original = OwningManager.Name + ".dll"; } foreach (var tu in DefaultXliffDocument.File.Body.TransUnits) { var tuTarget = xliffOriginal.File.Body.GetTransUnitForId(tu.Id); XLiffTransUnitVariant tuv = null; if (tuTarget != null) { tuv = tuTarget.GetVariantForLang(langId); } // REVIEW: should we write units with no translation (target)? var newTu = new XLiffTransUnit { Id = tu.Id, Dynamic = tu.Dynamic }; newTu.AddOrReplaceVariant(tu.GetVariantForLang(LocalizationManager.kDefaultLang)); if (tuv != null) { newTu.AddOrReplaceVariant(tuv); } newTu.Notes = tu.CopyNotes(); xliffOutput.AddTransUnit(newTu); } xliffOutput.File.Body.TransUnits.Sort(TuComparer); xliffOutput.Save(OwningManager.GetPathForLanguage(langId, true)); }
/// ------------------------------------------------------------------------------------ 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.TransUnits) { DefaultXliffDocument.File.Body.AddTransUnitOrVariantFromExisting(tu, LocalizationManager.kDefaultLang); } } } XliffDocuments.Add(LocalizationManager.kDefaultLang, DefaultXliffDocument); // Map the default language onto itself. LocalizationManagerInternal <XLiffDocument> .MapToExistingLanguage[LocalizationManager.kDefaultLang] = LocalizationManager.kDefaultLang; Exception error = null; foreach (var file in xliffFiles) { try { var xliffDoc = XLiffDocument.Read(file); var langId = xliffDoc.File.TargetLang; Debug.Assert(!string.IsNullOrEmpty(langId)); Debug.Assert(langId != LocalizationManager.kDefaultLang); // Provide a mapping from a specific variant of a language to the base language. // For example, "es-ES" can provide translations for "es" if we don't have "es" specifically. var pieces = langId.Split('-'); if (pieces.Length > 1) { if (!LocalizationManagerInternal <XLiffDocument> .MapToExistingLanguage.ContainsKey(pieces[0])) { LocalizationManagerInternal <XLiffDocument> .MapToExistingLanguage.Add(pieces[0], langId); } } // Identity mapping always wins. Storing it simplifies code elsewhere. LocalizationManagerInternal <XLiffDocument> .MapToExistingLanguage[langId] = langId; XliffDocuments.Add(langId, xliffDoc); var defunctUnits = new List <XLiffTransUnit>(); foreach (var tu in xliffDoc.File.Body.TransUnits) { // This block attempts to find 'orphans', that is, localizations that have been done using an obsolete ID. // We assume the default language Xliff has only current IDs, and therefore don't look for orphans in that case. // This guards against cases such as recently occurred in Bloom, where a dynamic ID EditTab.AddPageDialog.Title // was regarded as an obsolete id for PublishTab.Upload.Title if (langId != LocalizationManager.kDefaultLang && DefaultXliffDocument.GetTransUnitForId(tu.Id) == null && !tu.Id.EndsWith(kToolTipSuffix) && !tu.Id.EndsWith(kShortcutSuffix)) { //if we couldn't find it, maybe the id just changed and then if so re-id it. var movedUnit = DefaultXliffDocument.GetTransUnitForOrphan(tu); if (movedUnit == null) { // with dynamic strings, by definition we won't find them during a static code scan if (!tu.Dynamic) { defunctUnits.Add(tu); xliffDoc.IsDirty = true; IsDirty = true; } } else { if (xliffDoc.File.Body.TranslationsById.ContainsKey(tu.Id)) { // adjust the document's internal cache xliffDoc.File.Body.TranslationsById[movedUnit.Id] = xliffDoc.File.Body.TranslationsById[tu.Id]; xliffDoc.File.Body.TranslationsById.Remove(tu.Id); } tu.Id = movedUnit.Id; xliffDoc.IsDirty = true; IsDirty = true; } } } // Now we can delete any invalid XLiffTransUnit objects from this document. foreach (var tuBad in defunctUnits) { xliffDoc.File.Body.RemoveTransUnit(tuBad); } } catch (Exception e) { var msg = $"Caught exception in MergeXliffFilesIntoCache [{file}] - {e.Message}"; #if DEBUG throw new Exception(msg, e); #else // If an error happened reading some localization file other than one we care // about right now, just ignore it. if (file == OwningManager.GetPathForLanguage(LocalizationManager.UILanguageId, false)) { error = new Exception(msg, e); } #endif } } if (error != null) { throw error; } }