This class provides the ability to import translations for multi-lingual string fields in one or more lists. See FWR-1739 for motivation.
        /// <summary>
        /// If a localized lists file is available for the specified ws, load the information.
        /// Note: call this only when you are sure the WS is new to this project. It takes considerable time
        /// to run if it finds a localized list file.
        /// Our current strategy for loading localized lists is to have one file per localization.
        /// It is always LocalizedLists-XX.zip, where XX is the ICU locale for the writing system.
        /// (The zip file contains a single file, LocalizedLists-XX.xml.)
        /// So if such a file exists for this WS, we can import lists for that writing system.
        /// </summary>
        /// <param name="ws"></param>
        /// <param name="cache"></param>
        /// <param name="templateDir"></param>
        /// <param name="progress"> </param>
        public static void ImportTranslatedListsForWs(string ws, FdoCache cache, string templateDir, IProgress progress)
        {
            string path = TranslatedListsPathForWs(ws, templateDir);

            if (File.Exists(path))
            {
                var instance = new XmlTranslatedLists();
                NonUndoableUnitOfWorkHelper.DoUsingNewOrCurrentUOW(cache.ActionHandlerAccessor,
                                                                   () => instance.ImportTranslatedLists(path, cache, progress));
            }
        }
		/// <summary>
		/// If a localized lists file is available for the specified ws, load the information.
		/// Note: call this only when you are sure the WS is new to this project. It takes considerable time
		/// to run if it finds a localized list file.
		/// Our current strategy for loading localized lists is to have one file per localization.
		/// It is always LocalizedLists-XX.zip, where XX is the ICU locale for the writing system.
		/// (The zip file contains a single file, LocalizedLists-XX.xml.)
		/// So if such a file exists for this WS, we can import lists for that writing system.
		/// </summary>
		/// <param name="ws"></param>
		/// <param name="cache"></param>
		/// <param name="templateDir"></param>
		/// <param name="progress"> </param>
		public static void ImportTranslatedListsForWs(string ws, FdoCache cache, string templateDir, IProgress progress)
		{
			string path = TranslatedListsPathForWs(ws, templateDir);
			if (File.Exists(path))
			{
				var instance = new XmlTranslatedLists();
				NonUndoableUnitOfWorkHelper.DoUsingNewOrCurrentUOW(cache.ActionHandlerAccessor,
					() => instance.ImportTranslatedLists(path, cache, progress));
			}
		}
		public void ImportTranslatedListWithDuplicateSubitemNames()
		{
			var listDomains = m_cache.LangProject.LexDbOA.DomainTypesOA;
			Assert.AreEqual(0, listDomains.PossibilitiesOS.Count, "The Academic Domains list should be empty to start.");

			NonUndoableUnitOfWorkHelper.Do(m_cache.ActionHandlerAccessor, () => CreateStandardEnglishOnlyDomainTypes(listDomains));
			var xtl = new XmlTranslatedLists();
			var mapNameToItem = new Dictionary<string, ICmPossibility>();
			xtl.m_wsEn = m_wsEn;
			xtl.FillInMapForPossibilities(mapNameToItem, listDomains.PossibilitiesOS);
			Assert.AreEqual(3, listDomains.PossibilitiesOS.Count, "There should be only three toplevel Academic Domain possibilities.");
			Assert.AreEqual(11, mapNameToItem.Count, "There should be eleven Academic Domain possibilities in all for this test.");

			var reader = new StringReader(s_ksAcademicDomainTranslations);
			NonUndoableUnitOfWorkHelper.Do(m_cache.ActionHandlerAccessor, () => xtl.ImportTranslatedLists(reader, m_cache, null));

			var mapNameToItem2 = new Dictionary<string, ICmPossibility>();
			xtl.FillInMapForPossibilities(mapNameToItem2, listDomains.PossibilitiesOS);
			Assert.AreEqual(11, mapNameToItem2.Count, "Import should not add any new items: there should still be eleven Academic Domain possibilities.");

			foreach (var key in mapNameToItem.Keys)
			{
				var item = mapNameToItem[key];
				var item2 = mapNameToItem2[key];
				Assert.AreSame(item, item2, "The import should retain the existing items.");
				var keypath = key.Split(':');
				var name = keypath[keypath.Length - 1];
				var nameEnglish = item.Name.get_String(m_wsEn).Text;
				Assert.AreEqual(name, nameEnglish, "Import should not change the English name.");
				var nameFrench = item.Name.get_String(m_wsFr).Text;
				Assert.IsNotNullOrEmpty(nameFrench, "Every item should have a French name after import.");
				var abbrEnglish = item.Abbreviation.get_String(m_wsEn).Text;
				Assert.IsNotNullOrEmpty(abbrEnglish, "Every item should still have an English abbreviation after import.");
				var abbrFrench = item.Abbreviation.get_String(m_wsFr).Text;
				Assert.IsNotNullOrEmpty(abbrFrench, "Every item should have an French abbreviation after import.");
			}
			// Check first occurrence of "applied linguistics"
			var poss = mapNameToItem[":linguistics:applied linguistics"];
			var frenchName = poss.Name.get_String(m_wsFr).Text.Normalize(System.Text.NormalizationForm.FormC);
			Assert.AreEqual("linguistique appliquée", frenchName);
			var frenchAbbr = poss.Abbreviation.get_String(m_wsFr).Text;
			Assert.AreEqual("Ling app", frenchAbbr);
			// Check second occurrenence of "applied linguistics"
			poss = mapNameToItem[":sociolinguistics:applied linguistics"];
			frenchName = poss.Name.get_String(m_wsFr).Text.Normalize(System.Text.NormalizationForm.FormC);
			Assert.AreEqual("linguistique appliquée", frenchName);
			frenchAbbr = poss.Abbreviation.get_String(m_wsFr).Text;
			Assert.AreEqual("Ling app", frenchAbbr);
		}
		public void ImportTranslatedLists()
		{
			var listPOS = m_cache.LangProject.PartsOfSpeechOA;
			Assert.AreEqual(2, listPOS.PossibilitiesOS.Count);
			Assert.AreEqual(127, listPOS.Depth);
			Assert.AreEqual(-3, listPOS.WsSelector);
			Assert.IsTrue(listPOS.IsSorted);
			Assert.IsTrue(listPOS.UseExtendedFields);
			Assert.AreEqual(5049, listPOS.ItemClsid);
			Assert.AreEqual(1, listPOS.Abbreviation.StringCount);
			Assert.AreEqual("Pos", listPOS.Abbreviation.get_String(m_wsEn).Text);
			Assert.AreEqual(3, listPOS.Name.StringCount);
			Assert.AreEqual("Parts Of Speech", listPOS.Name.get_String(m_wsEn).Text);
			Assert.AreEqual("Categori\u0301as Grama\u0301ticas", listPOS.Name.get_String(m_wsEs).Text);
			Assert.AreEqual("Parties du Discours", listPOS.Name.get_String(m_wsFr).Text);

			List<IPartOfSpeech> allPartsOfSpeech = new List<IPartOfSpeech>();
			allPartsOfSpeech.AddRange(m_repoPOS.AllInstances());
			Assert.AreEqual(5, allPartsOfSpeech.Count);

			var listSemDom = m_cache.LangProject.SemanticDomainListOA;
			Assert.AreEqual(2, listSemDom.PossibilitiesOS.Count);
			Assert.AreEqual(127, listSemDom.Depth);
			Assert.AreEqual(-3, listSemDom.WsSelector);
			Assert.IsTrue(listSemDom.IsSorted);
			Assert.IsFalse(listSemDom.UseExtendedFields);
			Assert.AreEqual(66, listSemDom.ItemClsid);
			Assert.AreEqual(1, listSemDom.Abbreviation.StringCount);
			Assert.AreEqual("Sem", listSemDom.Abbreviation.get_String(m_wsEn).Text);
			Assert.AreEqual(1, listSemDom.Name.StringCount);
			Assert.AreEqual("Semantic Domains", listSemDom.Name.get_String(m_wsEn).Text);

			List<ICmSemanticDomain> allSemanticDomains = new List<ICmSemanticDomain>();
			allSemanticDomains.AddRange(m_repoSemDom.AllInstances());
			Assert.AreEqual(11, allSemanticDomains.Count);

			var xtl = new XmlTranslatedLists();
			var reader = new StringReader(s_ksTranslationsXml);
			NonUndoableUnitOfWorkHelper.Do(m_cache.ActionHandlerAccessor,
				() => xtl.ImportTranslatedLists(reader, m_cache, null));

			CheckPartsOfSpeech(listPOS, allPartsOfSpeech);
			CheckSemanticDomains(listSemDom, allSemanticDomains);
		}
		public void ImportTranslatedListWithSubclassTypes()
		{
			var listTypes = m_cache.LangProject.LexDbOA.VariantEntryTypesOA;

			var xtl = new XmlTranslatedLists();
			var mapNameToItem = new Dictionary<string, ICmPossibility>();

			xtl.m_wsEn = m_wsEn;
			xtl.FillInMapForPossibilities(mapNameToItem, listTypes.PossibilitiesOS);

			Assert.AreEqual(6, mapNameToItem.Count, "We should start with six variant types.");
			int countSubtypeItems = 0;
			foreach (var key in mapNameToItem.Keys)
			{
				var keypath = key.Split(':');
				var name = keypath[keypath.Length - 1];
				var item = mapNameToItem[key];
				var nameEnglish = item.Name.get_String(m_wsEn).Text;
				Assert.AreEqual(name, nameEnglish, "The list should have English names before import.");
				var nameFrench = item.Name.get_String(m_wsFr).Text;
				Assert.IsNullOrEmpty(nameFrench, "The original list should have no French names.");
				var abbrEnglish = item.Abbreviation.get_String(m_wsEn).Text;
				Assert.IsNotNullOrEmpty(abbrEnglish, "The list should have English abbreviations before import.");
				var abbrFrench = item.Abbreviation.get_String(m_wsFr).Text;
				Assert.IsNullOrEmpty(abbrFrench, "The original list should have no French abbreviations.");
				var descFrench = item.Description.get_String(m_wsFr).Text;
				Assert.IsNullOrEmpty(descFrench, "The original list should have no French descriptions.");
				var inflType = item as ILexEntryInflType;
				if (inflType != null)
				{
					++countSubtypeItems;
					var glossEnglish = inflType.GlossAppend.get_String(m_wsEn).Text;
					if (String.IsNullOrEmpty(glossEnglish))
					{
						// Test data isn't set up quite like default new project data, so tweak it a bit for later on.
						if (name == "Plural Variant")
							NonUndoableUnitOfWorkHelper.Do(m_cache.ActionHandlerAccessor, () => inflType.GlossAppend.set_String(m_wsEn, ".pl"));
						else if (name == "Past Variant")
							NonUndoableUnitOfWorkHelper.Do(m_cache.ActionHandlerAccessor, () => inflType.GlossAppend.set_String(m_wsEn, ".pst"));
					}
					var glossFrench = inflType.GlossAppend.get_String(m_wsFr).Text;
					Assert.IsNullOrEmpty(glossFrench, "The original list should have no French 'GlossAppend' values.");
				}
			}
			Assert.AreEqual(3, countSubtypeItems, "The list should have three ILexEntryInflType objects.");

			var reader = new StringReader(s_ksVariantTypesTranslations);
			NonUndoableUnitOfWorkHelper.Do(m_cache.ActionHandlerAccessor, () => xtl.ImportTranslatedLists(reader, m_cache, null));

			var mapNameToItem2 = new Dictionary<string, ICmPossibility>();
			xtl.FillInMapForPossibilities(mapNameToItem2, listTypes.PossibilitiesOS);
			Assert.AreEqual(6, mapNameToItem2.Count, "Import should not add any new items: there should still be six variant types.");

			int countNoFrench = 0;
			foreach (var key in mapNameToItem.Keys)
			{
				var item = mapNameToItem[key];
				var item2 = mapNameToItem2[key];
				Assert.AreSame(item, item2, "The import should retain the existing items.");
				var keypath = key.Split(':');
				var name = keypath[keypath.Length - 1];
				var nameEnglish = item.Name.get_String(m_wsEn).Text;
				Assert.AreEqual(name, nameEnglish, "Import should not change the English name.");
				var nameFrench = item.Name.get_String(m_wsFr).Text;
				Assert.IsNotNullOrEmpty(nameFrench, "The list should have French names after import.");
				var abbrEnglish = item.Abbreviation.get_String(m_wsEn).Text;
				Assert.IsNotNullOrEmpty(abbrEnglish, "The list should still have English abbreviations after import.");
				var abbrFrench = item.Abbreviation.get_String(m_wsFr).Text;
				Assert.IsNotNullOrEmpty(abbrFrench, "The list should have French abbreviations after import.");
				var descFrench = item.Description.get_String(m_wsFr).Text;
				Assert.IsNotNullOrEmpty(descFrench, "The list should have French descriptions after import.");
				var inflType = item as ILexEntryInflType;
				if (inflType != null)
				{
					var ga = inflType.GlossAppend;
					var cws = ga.StringCount;
					var ui = ga.UiString;
					var glossEnglish = inflType.GlossAppend.get_String(m_wsEn).Text;
					var glossFrench = inflType.GlossAppend.get_String(m_wsFr).Text;
					if (String.IsNullOrEmpty(glossEnglish))
					{
						++countNoFrench;
						Assert.IsNullOrEmpty(glossFrench, "The list should have no French if it has no English for 'GlossAppend' values.");
					}
					else
					{
						Assert.IsNotNullOrEmpty(glossFrench, "The list should have French if it has English for 'GlossAppend' values.");
					}
				}
			}
			Assert.AreEqual(1, countNoFrench, "Only one item should lack a French translation due to no English.");

			// Finally, just to be exhaustive, let's examine a few specific values.

			var type = mapNameToItem[":Dialectal Variant"] as ILexEntryType;
			var frenchName = type.Name.get_String(m_wsFr).Text;
			Assert.AreEqual("Variante Dialectale", frenchName);
			var typeinfl = type as ILexEntryInflType;
			Assert.IsNull(typeinfl);

			type = mapNameToItem[":Free Variant"] as ILexEntryType;
			frenchName = type.Name.get_String(m_wsFr).Text;
			Assert.AreEqual("Variante Gratuitement", frenchName);
			typeinfl = type as ILexEntryInflType;
			Assert.IsNull(typeinfl);

			type = mapNameToItem[":Irregular Inflectional Variant"] as ILexEntryType;
			frenchName = type.Name.get_String(m_wsFr).Text.Normalize(System.Text.NormalizationForm.FormC);
			Assert.AreEqual("Irrégulière Forme Fléchie", frenchName);
			typeinfl = type as ILexEntryInflType;
			Assert.IsNotNull(typeinfl);
			var frenchGlossAppend = typeinfl.GlossAppend.get_String(m_wsFr).Text;
			Assert.IsNullOrEmpty(frenchGlossAppend, "Irregular Inflectional Variant should not have a GlossAppend value.");

			type = mapNameToItem[":Plural Variant"] as ILexEntryType;
			frenchName = type.Name.get_String(m_wsFr).Text;
			Assert.AreEqual("Pluriel", frenchName);
			typeinfl = type as ILexEntryInflType;
			Assert.IsNotNull(typeinfl);
			frenchGlossAppend = typeinfl.GlossAppend.get_String(m_wsFr).Text;
			Assert.AreEqual(".plu", frenchGlossAppend);

			type = mapNameToItem[":Past Variant"] as ILexEntryType;
			frenchName = type.Name.get_String(m_wsFr).Text.Normalize(System.Text.NormalizationForm.FormC);
			Assert.AreEqual("Passé", frenchName);
			typeinfl = type as ILexEntryInflType;
			Assert.IsNotNull(typeinfl);
			frenchGlossAppend = typeinfl.GlossAppend.get_String(m_wsFr).Text;
			Assert.AreEqual(".pas", frenchGlossAppend);

			type = mapNameToItem[":Spelling Variant"] as ILexEntryType;
			frenchName = type.Name.get_String(m_wsFr).Text;
			Assert.AreEqual("Orthographe Variant", frenchName);
			typeinfl = type as ILexEntryInflType;
			Assert.IsNull(typeinfl);
		}