From 2583800b2cf61d6e5c674a353ece3caf6b78bc8d Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Tue, 7 Jan 2014 15:53:17 +0100 Subject: [PATCH 1/7] Proves dictionary repository fails to get when weird 0 languageid dictionary item exists. --- .../Repositories/DictionaryRepositoryTest.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs index 554c0f7ec4..d00249dd6a 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs @@ -66,6 +66,38 @@ namespace Umbraco.Tests.Persistence.Repositories } + [Test] + public void Get_Ignores_Item_WhenLanguageMissing() + { + // Arrange + var language = ServiceContext.LocalizationService.GetLanguageByCultureCode("en-US"); + var itemMissingLanguage = new DictionaryItem("I have invalid language"); + var translations = new List + { + new DictionaryTranslation(new Language("") { Id = 0 }, ""), + new DictionaryTranslation(language, "I have language") + }; + itemMissingLanguage.Translations = translations; + ServiceContext.LocalizationService.Save(itemMissingLanguage);//Id 3? + + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + LanguageRepository languageRepository; + using (var repository = CreateRepository(unitOfWork, out languageRepository)) + { + // Act + var dictionaryItem = repository.Get(3); + + // Assert + Assert.That(dictionaryItem, Is.Not.Null); + Assert.That(dictionaryItem.ItemKey, Is.EqualTo("I have invalid language")); + Assert.That(dictionaryItem.Translations.Any(), Is.True); + Assert.That(dictionaryItem.Translations.Any(x => x == null), Is.False); + Assert.That(dictionaryItem.Translations.First().Value, Is.EqualTo("I have language")); + Assert.That(dictionaryItem.Translations.Count(), Is.EqualTo(1)); + } + } + [Test] public void Can_Perform_GetAll_On_DictionaryRepository() { From 11a4a0436dae03ae3d6ae5ae06789bdfb0b13276 Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Tue, 7 Jan 2014 15:55:49 +0100 Subject: [PATCH 2/7] Proves LanguageRepository lines 37-38 to be useless, but probably intended --- .../Repositories/LanguageRepositoryTest.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs index c1ac5fa6a9..ce7f8b5d42 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs @@ -59,6 +59,22 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Get_WhenIdDoesntExist_ReturnsNull() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + using (var repository = CreateRepository(unitOfWork)) + { + // Act + var language = repository.Get(0); + + // Assert + Assert.That(language, Is.Null); + } + } + [Test] public void Can_Perform_GetAll_On_LanguageRepository() { From 0a68d2f4ee248895d0caacbf7c16204d5e06d781 Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Tue, 7 Jan 2014 16:03:26 +0100 Subject: [PATCH 3/7] Language repo now returns null for non-existing ids, making Get_WhenIdDoesntExist_ReturnsNull pass. --- src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs index d52a2549f4..0c891d512d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var languageDto = Database.First(sql); + var languageDto = Database.FirstOrDefault(sql); if (languageDto == null) return null; From 1408e4f64eda7a35158a612339e7b490ad8ebdbf Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Tue, 7 Jan 2014 16:15:59 +0100 Subject: [PATCH 4/7] DictionaryRepository now excludes translations with missing language. Should've committed a test to prove that TabsAndPropertiesResolver.TranslateTab uncommented fails, but can't find a nice existing fixture, so just debugging to see if it works now. --- .../Persistence/Repositories/DictionaryRepository.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs index 0b456cda91..39aaea3a78 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs @@ -50,6 +50,8 @@ namespace Umbraco.Core.Persistence.Repositories foreach (var textDto in dto.LanguageTextDtos) { var language = _languageRepository.Get(textDto.LanguageId); + if (language == null) + continue; var translationFactory = new DictionaryTranslationFactory(dto.UniqueId, language); list.Add(translationFactory.BuildEntity(textDto)); } From f7f6cc64ce1d0e8c5a0c64031abf679c176f5bc5 Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Tue, 7 Jan 2014 16:43:03 +0100 Subject: [PATCH 5/7] More or less fixes content localization. Must do more tests and fix create dialog. Only tested manually on my box. --- .../ContentPropertyDisplayConverter.cs | 4 +- .../Mapping/TabsAndPropertiesResolver.cs | 39 +++++++++++++------ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs index b1dd208e9f..7c72a753c8 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs @@ -32,8 +32,8 @@ namespace Umbraco.Web.Models.Mapping //set the display properties after mapping display.Alias = originalProp.Alias; - display.Description = originalProp.PropertyType.Description; - display.Label = originalProp.PropertyType.Name; + display.Description = TabsAndPropertiesResolver.TranslateItem(originalProp.PropertyType.Description); + display.Label = TabsAndPropertiesResolver.TranslateItem(originalProp.PropertyType.Name); display.HideLabel = valEditor.HideLabel; if (display.PropertyEditor == null) diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index e291ab17ea..4f67102da5 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -172,7 +172,7 @@ namespace Umbraco.Web.Models.Mapping { Id = rootGroup.Id, Alias = rootGroup.Name, - Label = TranslateTab(rootGroup.Name), + Label = TranslateItem(rootGroup.Name), Properties = aggregateProperties, IsActive = false }); @@ -198,33 +198,48 @@ namespace Umbraco.Web.Models.Mapping return aggregateTabs; } - private string TranslateTab(string tabName) + internal static string TranslateItem(string text) { - if (!tabName.StartsWith("#")) - return tabName; + if (!text.StartsWith("#")) + return text; - return tabName.Substring(1); + text = text.Substring(1); /* * The below currently doesnt work on my machine, since the dictonary always creates an entry with lang id = 0, but I dont have a lang id zero * so the query always fails, which is odd - * + * */ var local = ApplicationContext.Current.Services.LocalizationService; - var dic = local.GetDictionaryItemByKey(tabName); + var dic = local.GetDictionaryItemByKey(text); if (dic == null || !dic.Translations.Any()) - return tabName; + return text; + /*This code does not work at all with my config, languages doesn't have culturename, at least not lowercase. + * Changing to GetAll() and comparing cultures / parents, since en-uk is "en" for my admin user. + * var lang = local.GetLanguageByCultureCode(UmbracoContext.Current.Security.CurrentUser.Language); if (lang == null) return tabName; - - + */ + + /* Someone should probably really look into CurrentUser.Language. Lowercase??? en??? */ + var userCultureCode = UmbracoContext.Current.Security.CurrentUser.Language; + if (userCultureCode.Length > 2) + { + var parts = userCultureCode.Split('-', '_'); + userCultureCode = String.Format("{0}-{1}", parts[0], parts[1].ToUpper()); + } + var userCulture = System.Globalization.CultureInfo.GetCultureInfo(userCultureCode); + + var lang = local.GetAllLanguages() + .FirstOrDefault(l => l.CultureInfo.Equals(userCulture) || l.CultureInfo.Parent.Equals(userCulture)); + var translation = dic.Translations.Where(x => x.Language == lang).FirstOrDefault(); if (translation == null) - return tabName; + return text; - return translation.Value;*/ + return translation.Value; } } } From cbefabb72460f198e4a7e4dcb768f42a8c92ef68 Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Tue, 7 Jan 2014 17:44:01 +0100 Subject: [PATCH 6/7] Changed TabsAndPropertyResolver's translation mechanism to use CultureDictionaryFactory. Also added translation to ContentTypeController.GetAllowedChildren. Localization "complete" and working for content editing. --- .../Editors/ContentTypeController.cs | 54 +++++++++++++++---- .../ContentPropertyDisplayConverter.cs | 4 +- .../Mapping/TabsAndPropertiesResolver.cs | 53 +++++++----------- 3 files changed, 64 insertions(+), 47 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index e91dc4c90d..94574a7f75 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -2,6 +2,8 @@ using System.Net; using System.Web.Http; using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Dictionary; using Umbraco.Core.Models; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Mapping; @@ -24,6 +26,8 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] public class ContentTypeController : UmbracoAuthorizedJsonController { + private ICultureDictionary cultureDictionary; + /// /// Constructor /// @@ -47,28 +51,56 @@ namespace Umbraco.Web.Editors /// public IEnumerable GetAllowedChildren(int contentId) { + IEnumerable types; if (contentId == Core.Constants.System.Root) { - var types = Services.ContentTypeService.GetAllContentTypes(); + types = Services.ContentTypeService.GetAllContentTypes().ToList(); //if no allowed root types are set, just return everythibg if(types.Any(x => x.AllowedAsRoot)) types = types.Where(x => x.AllowedAsRoot); - - return types.Select(Mapper.Map); } - - var contentItem = Services.ContentService.GetById(contentId); - if (contentItem == null) + else { - throw new HttpResponseException(HttpStatusCode.NotFound); + var contentItem = Services.ContentService.GetById(contentId); + if (contentItem == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + types = contentItem.ContentType.AllowedContentTypes + .Select(x => Services.ContentTypeService.GetContentType(x.Id.Value)) + .ToList(); } + var basics = types.Select(Mapper.Map).ToList(); - return contentItem.ContentType.AllowedContentTypes - .Select(x => Services.ContentTypeService.GetContentType(x.Id.Value)) - .Select(Mapper.Map); - + foreach (var basic in basics) + { + basic.Name = TranslateItem(basic.Name); + basic.Description = TranslateItem(basic.Description); + } + + return basics; + } + + // This should really be centralized and used anywhere globalization applies. + internal string TranslateItem(string text) + { + if (!text.StartsWith("#")) + return text; + + text = text.Substring(1); + return CultureDictionary[text].IfNullOrWhiteSpace(text); + } + + private ICultureDictionary CultureDictionary + { + get + { + return + cultureDictionary ?? + (cultureDictionary = CultureDictionaryFactoryResolver.Current.Factory.CreateDictionary()); + } } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs index 7c72a753c8..b1dd208e9f 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs @@ -32,8 +32,8 @@ namespace Umbraco.Web.Models.Mapping //set the display properties after mapping display.Alias = originalProp.Alias; - display.Description = TabsAndPropertiesResolver.TranslateItem(originalProp.PropertyType.Description); - display.Label = TabsAndPropertiesResolver.TranslateItem(originalProp.PropertyType.Name); + display.Description = originalProp.PropertyType.Description; + display.Label = originalProp.PropertyType.Name; display.HideLabel = valEditor.HideLabel; if (display.PropertyEditor == null) diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index 4f67102da5..dc99f70c58 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using AutoMapper; using Umbraco.Core; +using Umbraco.Core.Dictionary; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Web.Models.ContentEditing; @@ -15,6 +16,7 @@ namespace Umbraco.Web.Models.Mapping /// internal class TabsAndPropertiesResolver : ValueResolver>> { + private ICultureDictionary cultureDictionary; protected IEnumerable IgnoreProperties { get; set; } public TabsAndPropertiesResolver() @@ -165,6 +167,13 @@ namespace Umbraco.Web.Models.Mapping Mapper.Map, IEnumerable>( propsForGroup)); } + + // Not sure whether it's a good idea to add this to the ContentPropertyDisplay mapper + foreach (var prop in aggregateProperties) + { + prop.Label = TranslateItem(prop.Label); + prop.Description = TranslateItem(prop.Description); + } //then we'll just use the root group's data to make the composite tab var rootGroup = propertyGroups.Single(x => x.ParentId == null); @@ -198,48 +207,24 @@ namespace Umbraco.Web.Models.Mapping return aggregateTabs; } - internal static string TranslateItem(string text) + // This should really be centralized and used anywhere globalization applies. + internal string TranslateItem(string text) { - if (!text.StartsWith("#")) return text; text = text.Substring(1); + return CultureDictionary[text].IfNullOrWhiteSpace(text); + } - /* - * The below currently doesnt work on my machine, since the dictonary always creates an entry with lang id = 0, but I dont have a lang id zero - * so the query always fails, which is odd - * */ - var local = ApplicationContext.Current.Services.LocalizationService; - var dic = local.GetDictionaryItemByKey(text); - if (dic == null || !dic.Translations.Any()) - return text; - - /*This code does not work at all with my config, languages doesn't have culturename, at least not lowercase. - * Changing to GetAll() and comparing cultures / parents, since en-uk is "en" for my admin user. - * - var lang = local.GetLanguageByCultureCode(UmbracoContext.Current.Security.CurrentUser.Language); - if (lang == null) - return tabName; - */ - - /* Someone should probably really look into CurrentUser.Language. Lowercase??? en??? */ - var userCultureCode = UmbracoContext.Current.Security.CurrentUser.Language; - if (userCultureCode.Length > 2) + private ICultureDictionary CultureDictionary + { + get { - var parts = userCultureCode.Split('-', '_'); - userCultureCode = String.Format("{0}-{1}", parts[0], parts[1].ToUpper()); + return + cultureDictionary ?? + (cultureDictionary = CultureDictionaryFactoryResolver.Current.Factory.CreateDictionary()); } - var userCulture = System.Globalization.CultureInfo.GetCultureInfo(userCultureCode); - - var lang = local.GetAllLanguages() - .FirstOrDefault(l => l.CultureInfo.Equals(userCulture) || l.CultureInfo.Parent.Equals(userCulture)); - - var translation = dic.Translations.Where(x => x.Language == lang).FirstOrDefault(); - if (translation == null) - return text; - - return translation.Value; } } } From 6eeb9d6af46d173e864875d5ad3623656c91f4c8 Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Tue, 7 Jan 2014 18:53:47 +0100 Subject: [PATCH 7/7] Forgot document type on general properties tab --- .../Models/Mapping/TabsAndPropertiesResolver.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index dc99f70c58..95a46b0e27 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -94,7 +94,7 @@ namespace Umbraco.Web.Models.Mapping { Alias = string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix), Label = ui.Text("content", "documentType"), - Value = display.ContentTypeName, + Value = TranslateItem(display.ContentTypeName, CreateDictionary()), View = labelEditor } }; @@ -209,12 +209,18 @@ namespace Umbraco.Web.Models.Mapping // This should really be centralized and used anywhere globalization applies. internal string TranslateItem(string text) + { + var cultureDictionary = CultureDictionary; + return TranslateItem(text, cultureDictionary); + } + + private static string TranslateItem(string text, ICultureDictionary cultureDictionary) { if (!text.StartsWith("#")) return text; text = text.Substring(1); - return CultureDictionary[text].IfNullOrWhiteSpace(text); + return cultureDictionary[text].IfNullOrWhiteSpace(text); } private ICultureDictionary CultureDictionary @@ -223,8 +229,13 @@ namespace Umbraco.Web.Models.Mapping { return cultureDictionary ?? - (cultureDictionary = CultureDictionaryFactoryResolver.Current.Factory.CreateDictionary()); + (cultureDictionary = CreateDictionary()); } } + + private static ICultureDictionary CreateDictionary() + { + return CultureDictionaryFactoryResolver.Current.Factory.CreateDictionary(); + } } }