diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bc8d37..ba0ced9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [L10NSharp.Windows.Forms] Restored project-local Resources support for `FallbackLanguagesDlgBase` button images (`Move`, `Move_up`, and `Move_down`). - [L10NSharp.Windows.Forms] Corrected resource manager base name to `L10NSharp.Windows.Forms.Properties.Resources`. - [L10NSharp.Windows.Forms.Tests] Corrected resource manager base name to `L10NSharp.Windows.Forms.Tests.Properties.Resources`. +- [L10NSharp] Fixed `FilenamesToAddToCache` yielding both the custom and installed XLIFF for the same language when `UseLanguageCodeFolders` is `true`, causing custom translations to be silently overwritten by installed ones. (#140) - [L10NSharp] Fixed `ExtractXliff` accumulating duplicate "Not found in static scan" notes on successive runs; the note is now replaced rather than appended, and removed when the string is subsequently found. (#113) - [L10NSharp] Fixed `LocalizationManager.GetString` silently falling back to English when called with a one-shot `IEnumerable` for `preferredLanguageIds`; the sequence is now materialized before use. (#139) diff --git a/src/L10NSharp.Tests/LocalizationManagerTestsBase.cs b/src/L10NSharp.Tests/LocalizationManagerTestsBase.cs index e312e1d..13d8bd2 100644 --- a/src/L10NSharp.Tests/LocalizationManagerTestsBase.cs +++ b/src/L10NSharp.Tests/LocalizationManagerTestsBase.cs @@ -13,7 +13,7 @@ namespace L10NSharp.Tests { - public abstract class LocalizationManagerTestsBase where T: IDocument + public abstract class LocalizationManagerTestsBase where T : IDocument { protected const string AppId = "test"; protected const string AppName = "unit test"; @@ -406,39 +406,25 @@ public void GetUiLanguages_FindsAll() [Test] public void GetUiLanguages_FindsAllWithFolders() { - try - { - LocalizationManager.UseLanguageCodeFolders = true; - using (var folder = new TempFolder()) - { - SetupManager(folder); - var cultures = new List(LocalizationManager.GetUILanguages(true)); - Assert.AreEqual(4, cultures.Count); - CollectionAssert.AreEquivalent(new[] { "ar", "en", "fr", "es" }, cultures.Select(c => c.IetfLanguageTag).ToArray()); - Assert.That(cultures, Is.Ordered.By("DisplayName")); - } - } - finally + LocalizationManager.UseLanguageCodeFolders = true; + using (var folder = new TempFolder()) { - LocalizationManager.UseLanguageCodeFolders = false; + SetupManager(folder); + var cultures = new List(LocalizationManager.GetUILanguages(true)); + Assert.AreEqual(4, cultures.Count); + CollectionAssert.AreEquivalent(new[] { "ar", "en", "fr", "es" }, cultures.Select(c => c.IetfLanguageTag).ToArray()); + Assert.That(cultures, Is.Ordered.By("DisplayName")); } } [Test] public void GetDynamicStringInEnglish_NoDefault_FindsEnglishWithFolders() { - try - { - LocalizationManager.UseLanguageCodeFolders = true; - using (var folder = new TempFolder()) - { - SetupManager(folder); - Assert.That(LocalizationManager.GetDynamicString(AppId, "blahId", null), Is.EqualTo("blah"), "With no default supplied, should find saved English"); - } - } - finally + LocalizationManager.UseLanguageCodeFolders = true; + using (var folder = new TempFolder()) { - LocalizationManager.UseLanguageCodeFolders = false; + SetupManager(folder); + Assert.That(LocalizationManager.GetDynamicString(AppId, "blahId", null), Is.EqualTo("blah"), "With no default supplied, should find saved English"); } } @@ -474,6 +460,50 @@ public void GetString_OverloadThatTakesListOfLanguages_WorksWithFolders(IEnumera } } + /// + /// When UseLanguageCodeFolders is true and both an installed and a user-modified file + /// exist for the same language, the custom (user-modified) file must win. + /// + [Test] + public void CustomTranslation_TakesPrecedenceOverInstalled_WithLanguageCodeFolders() + { + LocalizationManager.UseLanguageCodeFolders = true; + using (var folder = new TempFolder()) + { + // Set up the English default in the installed directory. + AddEnglishTranslation(GetInstalledDirectory(folder), "1.0"); + + // Set up an installed Arabic translation with value "inArabic". + AddArabicTranslation(GetInstalledDirectory(folder)); + + // Set up a user-modified Arabic translation for the same "theId" string + // with a different value to confirm the custom one wins. + var customArabicDoc = CreateNewDocument(null, "en", "ar"); + var customTu = CreateTransUnit("theId", false, + CreateTransUnitVariant("en", "wrong"), + CreateTransUnitVariant("ar", "custom arabic translation"), + "Test", TranslationStatus.Approved); + customArabicDoc.AddTransUnit(customTu); + var customArabicPath = Path.Combine(GetUserModifiedDirectory(folder), + LocalizationManager.GetTranslationFileNameForLanguage(AppId, "ar")); + Directory.CreateDirectory(Path.GetDirectoryName(customArabicPath)); + customArabicDoc.Save(customArabicPath); + + // Set up the localization manager. + var manager = CreateLocalizationManager(AppId, AppName, AppVersion, + GetInstalledDirectory(folder), GetGeneratedDirectory(folder), + GetUserModifiedDirectory(folder)); + LocalizationManagerInternal.LoadedManagers[AppId] = manager; + + LocalizationManager.SetUILanguage("ar"); + + // SUT: the custom translation must win over the installed one. + Assert.AreEqual("custom arabic translation", + LocalizationManager.GetString("theId", "default"), + "Custom (user-modified) translation should take precedence over installed translation when UseLanguageCodeFolders is true"); + } + } + [Test] public void GetUiLanguages_AzeriHasHackedNativeName() { diff --git a/src/L10NSharp/XLiffUtils/XliffLocalizationManager.cs b/src/L10NSharp/XLiffUtils/XliffLocalizationManager.cs index 9fc5b55..361562a 100644 --- a/src/L10NSharp/XLiffUtils/XliffLocalizationManager.cs +++ b/src/L10NSharp/XLiffUtils/XliffLocalizationManager.cs @@ -369,8 +369,11 @@ public IEnumerable FilenamesToAddToCache if (string.IsNullOrEmpty(langId) || langId == LocalizationManager.kDefaultLang) continue; - langIdsOfCustomizedLocales.Add(langId); - yield return xliffFile; + if (!langIdsOfCustomizedLocales.Contains(langId)) + { + langIdsOfCustomizedLocales.Add(langId); + yield return xliffFile; + } } } else