From 38789d821cbf84507993ad0efbac5bc46606c364 Mon Sep 17 00:00:00 2001 From: Johan Larsson Date: Sun, 17 Apr 2016 22:43:01 +0200 Subject: [PATCH 1/6] wip --- .../Gu.Localization.Tests.csproj | 3 +- .../ValidateTests.Formats.cs | 20 +++ .../ValidateTests.Translations.cs | 140 ++++++++++++++++++ Gu.Localization.Tests/ValidateTests.cs | 109 -------------- Gu.Localization/Validate.Formats.cs | 18 ++- 5 files changed, 179 insertions(+), 111 deletions(-) create mode 100644 Gu.Localization.Tests/ValidateTests.Formats.cs create mode 100644 Gu.Localization.Tests/ValidateTests.Translations.cs delete mode 100644 Gu.Localization.Tests/ValidateTests.cs diff --git a/Gu.Localization.Tests/Gu.Localization.Tests.csproj b/Gu.Localization.Tests/Gu.Localization.Tests.csproj index 071eadb6..a9bece83 100644 --- a/Gu.Localization.Tests/Gu.Localization.Tests.csproj +++ b/Gu.Localization.Tests/Gu.Localization.Tests.csproj @@ -54,7 +54,8 @@ - + + diff --git a/Gu.Localization.Tests/ValidateTests.Formats.cs b/Gu.Localization.Tests/ValidateTests.Formats.cs new file mode 100644 index 00000000..1dde7adf --- /dev/null +++ b/Gu.Localization.Tests/ValidateTests.Formats.cs @@ -0,0 +1,20 @@ +namespace Gu.Localization.Tests +{ + using System; + + using NUnit.Framework; + + public partial class ValidateTests + { + public class Formats + { + [TestCase("Hej")] + [TestCase("First: {0}, Second: {0}")] + public void ThrowsOneArgument(string format) + { + Assert.Throws(() => Validate.Format(format, 1)); + Assert.IsFalse(format, Validate.IsValidFormat(format, 1)); + } + } + } +} diff --git a/Gu.Localization.Tests/ValidateTests.Translations.cs b/Gu.Localization.Tests/ValidateTests.Translations.cs new file mode 100644 index 00000000..a275e38a --- /dev/null +++ b/Gu.Localization.Tests/ValidateTests.Translations.cs @@ -0,0 +1,140 @@ +namespace Gu.Localization.Tests +{ + using System; + using System.Globalization; + using System.Text; + + using Gu.Localization.Tests.Errors; + + using NUnit.Framework; + + public partial class ValidateTests + { + public class Translations + { + [Test] + public void ForResourceManager() + { + var errors = Validate.Translations(Properties.Resources.ResourceManager); + Assert.IsFalse(errors.IsEmpty); + CollectionAssert.AreEqual( + new[] { "NeutralOnly", "EnglishOnly", "NoTranslation", "Value___0_" }, + errors.Keys); + var builder = new StringBuilder(); + builder.AppendLine("Key: NeutralOnly") + .AppendLine(" Missing for: { de, en, sv }") + .AppendLine("Key: EnglishOnly") + .AppendLine(" Missing for: { de, sv }") + .AppendLine("Key: NoTranslation") + .AppendLine(" Missing for: { de, en, sv }") + .AppendLine("Key: Value___0_") + .AppendLine(" Has format errors, the formats are:") + .AppendLine(" Value: {0}") + .AppendLine(" null") + .AppendLine(" Value: {0} {1}") + .AppendLine(" Värde: ") + .AppendLine(" Missing for: { de }"); + var expected = builder.ToString(); + var actual = errors.ToString(" ", Environment.NewLine); + Assert.AreEqual(expected, actual); + } + + [Test] + public void TranslationsForResourceManagerExplicitCultures() + { + var cultures = new[] { CultureInfo.GetCultureInfo("sv"), CultureInfo.GetCultureInfo("en") }; + var errors = Validate.Translations(Properties.Resources.ResourceManager, cultures); + Assert.IsFalse(errors.IsEmpty); + CollectionAssert.AreEqual( + new[] { "NeutralOnly", "EnglishOnly", "NoTranslation", "Value___0_" }, + errors.Keys); + var builder = new StringBuilder(); + builder.AppendLine("Key: NeutralOnly") + .AppendLine(" Missing for: { sv, en }") + .AppendLine("Key: EnglishOnly") + .AppendLine(" Missing for: { sv }") + .AppendLine("Key: NoTranslation") + .AppendLine(" Missing for: { sv, en }") + .AppendLine("Key: Value___0_") + .AppendLine(" Has format errors, the formats are:") + .AppendLine(" Värde: ") + .AppendLine(" Value: {0} {1}"); + var expected = builder.ToString(); + var actual = errors.ToString(" ", Environment.NewLine); + Assert.AreEqual(expected, actual); + } + + [Test] + public void EnumTranslations() + { + var errors = Validate.EnumTranslations(Properties.Resources.ResourceManager); + Assert.IsFalse(errors.IsEmpty); + CollectionAssert.AreEqual(new[] { DummyEnum.MissingTranslation.ToString() }, errors.Keys); + Assert.AreEqual( + "Key: MissingTranslation Missing for: { invariant, de, en, sv } ", + errors.ToString("", " ")); + } + + [Test] + public void EnumTranslationsExplicitCultures() + { + var cultures = new[] { CultureInfo.GetCultureInfo("sv"), CultureInfo.GetCultureInfo("en") }; + var errors = Validate.EnumTranslations(Properties.Resources.ResourceManager, cultures); + Assert.IsFalse(errors.IsEmpty); + CollectionAssert.AreEqual(new[] { DummyEnum.MissingTranslation.ToString() }, errors.Keys); + Assert.AreEqual("Key: MissingTranslation Missing for: { sv, en } ", errors.ToString("", " ")); + } + + [Test] + public void FormatsHappyPath() + { + var errors = Validate.Translations( + Properties.Resources.ResourceManager, + nameof(Properties.Resources.first___0___second__1_)); + CollectionAssert.IsEmpty(errors); + + var cultures = new[] + { + CultureInfo.InvariantCulture, + CultureInfo.GetCultureInfo("en"), + CultureInfo.GetCultureInfo("sv") + }; + errors = Validate.Translations( + Properties.Resources.ResourceManager, + nameof(Properties.Resources.first___0___second__1_), + cultures); + CollectionAssert.IsEmpty(errors); + } + + [Test] + public void FormatsWithErrors() + { + var errors = Validate.Translations( + Properties.Resources.ResourceManager, + Properties.Resources.Value___0_); + CollectionAssert.IsNotEmpty(errors); + } + + [Test] + public void TranslationsForKeyWhenNoErrors() + { + var errors = Validate.Translations( + Properties.Resources.ResourceManager, + nameof(Properties.Resources.AllLanguages)); + CollectionAssert.IsEmpty(errors); + + var cultures = new[] + { + CultureInfo.InvariantCulture, + CultureInfo.GetCultureInfo("en"), + CultureInfo.GetCultureInfo("sv") + }; + errors = Validate.Translations( + Properties.Resources.ResourceManager, + nameof(Properties.Resources.AllLanguages), + cultures); + CollectionAssert.IsEmpty(errors); + } + } + } +} diff --git a/Gu.Localization.Tests/ValidateTests.cs b/Gu.Localization.Tests/ValidateTests.cs deleted file mode 100644 index e9bc1862..00000000 --- a/Gu.Localization.Tests/ValidateTests.cs +++ /dev/null @@ -1,109 +0,0 @@ -namespace Gu.Localization.Tests -{ - using System; - using System.Globalization; - using System.Text; - - using Gu.Localization.Tests.Errors; - - using NUnit.Framework; - - public class ValidateTests - { - [Test] - public void TranslationsForResourceManager() - { - var errors = Validate.Translations(Properties.Resources.ResourceManager); - Assert.IsFalse(errors.IsEmpty); - CollectionAssert.AreEqual(new[] { "NeutralOnly", "EnglishOnly", "NoTranslation", "Value___0_" }, errors.Keys); - var builder = new StringBuilder(); - builder.AppendLine("Key: NeutralOnly") - .AppendLine(" Missing for: { de, en, sv }") - .AppendLine("Key: EnglishOnly") - .AppendLine(" Missing for: { de, sv }") - .AppendLine("Key: NoTranslation") - .AppendLine(" Missing for: { de, en, sv }") - .AppendLine("Key: Value___0_") - .AppendLine(" Has format errors, the formats are:") - .AppendLine(" Value: {0}") - .AppendLine(" null") - .AppendLine(" Value: {0} {1}") - .AppendLine(" Värde: ") - .AppendLine(" Missing for: { de }"); - var expected = builder.ToString(); - var actual = errors.ToString(" ", Environment.NewLine); - Assert.AreEqual(expected, actual); - } - - [Test] - public void TranslationsForResourceManagerExplicitCultures() - { - var cultures = new[] { CultureInfo.GetCultureInfo("sv"), CultureInfo.GetCultureInfo("en") }; - var errors = Validate.Translations(Properties.Resources.ResourceManager, cultures); - Assert.IsFalse(errors.IsEmpty); - CollectionAssert.AreEqual(new[] { "NeutralOnly", "EnglishOnly", "NoTranslation", "Value___0_" }, errors.Keys); - var builder = new StringBuilder(); - builder.AppendLine("Key: NeutralOnly") - .AppendLine(" Missing for: { sv, en }") - .AppendLine("Key: EnglishOnly") - .AppendLine(" Missing for: { sv }") - .AppendLine("Key: NoTranslation") - .AppendLine(" Missing for: { sv, en }") - .AppendLine("Key: Value___0_") - .AppendLine(" Has format errors, the formats are:") - .AppendLine(" Värde: ") - .AppendLine(" Value: {0} {1}"); - var expected = builder.ToString(); - var actual = errors.ToString(" ", Environment.NewLine); - Assert.AreEqual(expected, actual); - } - - [Test] - public void EnumTranslations() - { - var errors = Validate.EnumTranslations(Properties.Resources.ResourceManager); - Assert.IsFalse(errors.IsEmpty); - CollectionAssert.AreEqual(new[] { DummyEnum.MissingTranslation.ToString() }, errors.Keys); - Assert.AreEqual("Key: MissingTranslation Missing for: { invariant, de, en, sv } ", errors.ToString("", " ")); - } - - [Test] - public void EnumTranslationsExplicitCultures() - { - var cultures = new[] { CultureInfo.GetCultureInfo("sv"), CultureInfo.GetCultureInfo("en") }; - var errors = Validate.EnumTranslations(Properties.Resources.ResourceManager, cultures); - Assert.IsFalse(errors.IsEmpty); - CollectionAssert.AreEqual(new[] { DummyEnum.MissingTranslation.ToString() }, errors.Keys); - Assert.AreEqual("Key: MissingTranslation Missing for: { sv, en } ", errors.ToString("", " ")); - } - - [Test] - public void FormatsHappyPath() - { - var errors = Validate.Translations(Properties.Resources.ResourceManager, nameof(Properties.Resources.first___0___second__1_)); - CollectionAssert.IsEmpty(errors); - - var cultures = new[] { CultureInfo.InvariantCulture, CultureInfo.GetCultureInfo("en"), CultureInfo.GetCultureInfo("sv") }; - errors = Validate.Translations(Properties.Resources.ResourceManager, nameof(Properties.Resources.first___0___second__1_), cultures); - CollectionAssert.IsEmpty(errors); - } - - [Test] - public void FormatsWithErrors() - { - var errors = Validate.Translations(Properties.Resources.ResourceManager, Properties.Resources.Value___0_); - CollectionAssert.IsNotEmpty(errors); - } - - [Test] - public void TranslationsForKeyWhenNoErrors() - { - var errors = Validate.Translations(Properties.Resources.ResourceManager, nameof(Properties.Resources.AllLanguages)); - CollectionAssert.IsEmpty(errors); - - var cultures = new[] { CultureInfo.InvariantCulture, CultureInfo.GetCultureInfo("en"), CultureInfo.GetCultureInfo("sv") }; - errors = Validate.Translations(Properties.Resources.ResourceManager, nameof(Properties.Resources.AllLanguages), cultures); - CollectionAssert.IsEmpty(errors); - } - } -} diff --git a/Gu.Localization/Validate.Formats.cs b/Gu.Localization/Validate.Formats.cs index 13e35432..a9260617 100644 --- a/Gu.Localization/Validate.Formats.cs +++ b/Gu.Localization/Validate.Formats.cs @@ -4,7 +4,7 @@ public partial class Validate { - internal static void Format(string format, object arg) + public static void Format(string format, T arg) { int count; bool? anyItemHasFormat; @@ -13,10 +13,26 @@ internal static void Format(string format, object arg) throw new FormatException($"Invalid format string: {format}."); } + throw new NotImplementedException("check if T is IFormattable if(anyItemHasFormat == true) "); + if (count != 1) { throw new FormatException($"Invalid format string: {format} for the single argument: {arg}."); } } + + public static bool IsValidFormat(string format, T arg) + { + int count; + bool? anyItemHasFormat; + if (!FormatString.IsValidFormat(format, out count, out anyItemHasFormat)) + { + return false; + } + + throw new NotImplementedException("check if T is IFormattable if(anyItemHasFormat == true) "); + + return count == 1; + } } } From d1dc171598326b5ffe3bda3440f9aaaadaeec8ab Mon Sep 17 00:00:00 2001 From: Johan Larsson Date: Mon, 18 Apr 2016 10:42:43 +0200 Subject: [PATCH 2/6] wip --- .../Gu.Localization.Tests.csproj | 1 + .../Internals/ResourceManagerExtTests.cs | 2 - .../TranslatorTests.Parameters.cs | 5 -- .../ValidateTests.Formats.cs | 17 +++- Gu.Localization/Gu.Localization.csproj | 1 + .../Internals/ResourceManagerExt.cs | 70 +++++++-------- Gu.Localization/Internals/ResourceManagers.cs | 87 ++++++++++++++++-- .../Properties/Resources.Designer.cs | 9 ++ Gu.Localization/Properties/Resources.resx | 3 + Gu.Localization/Translator.Parameters.cs | 57 ++++++++++++ Gu.Localization/Translator.cs | 43 +-------- Gu.Localization/Validate.Formats.cs | 21 +++-- Gu.Localization/Validate.Translations.cs | 88 +++++++++++-------- 13 files changed, 267 insertions(+), 137 deletions(-) create mode 100644 Gu.Localization/Translator.Parameters.cs diff --git a/Gu.Localization.Tests/Gu.Localization.Tests.csproj b/Gu.Localization.Tests/Gu.Localization.Tests.csproj index a9bece83..4a0028b8 100644 --- a/Gu.Localization.Tests/Gu.Localization.Tests.csproj +++ b/Gu.Localization.Tests/Gu.Localization.Tests.csproj @@ -83,6 +83,7 @@ ResXFileCodeGenerator Resources.Designer.cs + Designer diff --git a/Gu.Localization.Tests/Internals/ResourceManagerExtTests.cs b/Gu.Localization.Tests/Internals/ResourceManagerExtTests.cs index 6cb343c1..98aa3d3b 100644 --- a/Gu.Localization.Tests/Internals/ResourceManagerExtTests.cs +++ b/Gu.Localization.Tests/Internals/ResourceManagerExtTests.cs @@ -30,8 +30,6 @@ public void HasKey(string key, string cultureName, bool expected) : CultureInfo.GetCultureInfo(cultureName); var resourceManager = Properties.Resources.ResourceManager; - resourceManager.GetString(key, culture); // warmup - Assert.AreEqual(expected, resourceManager.HasKey(key, culture)); Assert.IsNull(resourceManager.GetResourceSet(culture, false, false)); } diff --git a/Gu.Localization.Tests/TranslatorTests.Parameters.cs b/Gu.Localization.Tests/TranslatorTests.Parameters.cs index f80833f4..ebcf53a6 100644 --- a/Gu.Localization.Tests/TranslatorTests.Parameters.cs +++ b/Gu.Localization.Tests/TranslatorTests.Parameters.cs @@ -13,17 +13,12 @@ public class Parameters [TestCase(null, 1, "Neutral: 1")] public void TranslateOneParameterThrow(string cultureName, object arg, string expected) { - Assert.Inconclusive(); var culture = cultureName != null ? CultureInfo.GetCultureInfo(cultureName) : CultureInfo.InvariantCulture; Translator.CurrentCulture = culture; var actual = Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.Value___0_), arg); Assert.AreEqual(expected, actual); - - Translator.CurrentCulture = CultureInfo.GetCultureInfo("it"); - actual = Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.Value___0_), culture, arg); - Assert.AreEqual(expected, actual); } [Test] diff --git a/Gu.Localization.Tests/ValidateTests.Formats.cs b/Gu.Localization.Tests/ValidateTests.Formats.cs index 1dde7adf..ee05acef 100644 --- a/Gu.Localization.Tests/ValidateTests.Formats.cs +++ b/Gu.Localization.Tests/ValidateTests.Formats.cs @@ -9,11 +9,22 @@ public partial class ValidateTests public class Formats { [TestCase("Hej")] - [TestCase("First: {0}, Second: {0}")] - public void ThrowsOneArgument(string format) + [TestCase("First: {1}")] + [TestCase("First: {0}, Second: {1}")] + public void OneArgumentWithError(string format) { Assert.Throws(() => Validate.Format(format, 1)); - Assert.IsFalse(format, Validate.IsValidFormat(format, 1)); + Assert.IsFalse(Validate.IsValidFormat(format, 1)); + } + + [TestCase("First: {0}")] + [TestCase("First: {0:N}")] + [TestCase("First: {0}, Second: {0}")] + [TestCase("First: {0:F2}, Second: {0:F3}")] + public void OneArgumentHappyPath(string format) + { + Assert.DoesNotThrow(() => Validate.Format(format, 1)); + Assert.IsTrue(Validate.IsValidFormat(format, 1)); } } } diff --git a/Gu.Localization/Gu.Localization.csproj b/Gu.Localization/Gu.Localization.csproj index 7072693e..8931c5de 100644 --- a/Gu.Localization/Gu.Localization.csproj +++ b/Gu.Localization/Gu.Localization.csproj @@ -71,6 +71,7 @@ + diff --git a/Gu.Localization/Internals/ResourceManagerExt.cs b/Gu.Localization/Internals/ResourceManagerExt.cs index d3e37aac..d11d49e8 100644 --- a/Gu.Localization/Internals/ResourceManagerExt.cs +++ b/Gu.Localization/Internals/ResourceManagerExt.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Globalization; using System.Linq; - using System.Reflection; using System.Resources; internal static class ResourceManagerExt @@ -21,10 +20,18 @@ internal static class ResourceManagerExt /// True if a translation exists internal static bool HasKey(this ResourceManager resourceManager, string key, CultureInfo culture) { - using (var resourceSet = resourceManager.GetTempResourceSet(culture)) + using (var clone = resourceManager.Clone()) { - return resourceSet?.ResourceSet.OfType() - .Any(x => Equals(x.Key, key)) == true; + if (clone?.ResourceManager == null) + { + return false; + } + + using (var resourceSet = clone.ResourceManager.GetResourceSet(culture, true, false)) + { + return resourceSet?.OfType() + .Any(x => Equals(x.Key, key)) == true; + } } } @@ -38,56 +45,47 @@ internal static bool HasKey(this ResourceManager resourceManager, string key, Cu /// True if a translation exists internal static bool HasCulture(this ResourceManager resourceManager, CultureInfo culture) { - using (var resourceSet = resourceManager.GetTempResourceSet(culture)) + using (var clone = resourceManager.Clone()) { - return resourceSet != null; + return clone?.ResourceManager?.GetResourceSet(culture, true, false) != null; } } - // Clones the resourcemanager and gets the resource set for the culture + // Clones the resourcemanager // This is slow and backwards but can't think of another way that does not load a the resourceset into memory. // Also calling resourceManager.ReleaseAllResources() feels really nasty in a lib like this. // Keeping it slow and dumb until something better. - private static Disposer GetTempResourceSet(this ResourceManager resourceManager, CultureInfo culture) + internal static ResourceManagerClone Clone(this ResourceManager resourceManager) { - var type = AppDomain.CurrentDomain.GetAssemblies() - .Select(x => x.GetType(resourceManager.BaseName)) - .SingleOrDefault(x => x != null && - x.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static) - .Any(p => p.PropertyType == typeof(ResourceManager))); - if (type == null) - { - return null; - } - - var clone = new ResourceManager(resourceManager.BaseName, type.Assembly); - var resourceSet = clone.GetResourceSet(culture, true, false); - if (resourceSet == null) - { - resourceManager.ReleaseAllResources(); - return null; - } + return new ResourceManagerClone(resourceManager); + } - return new Disposer(resourceManager, resourceSet); + internal static Type ContainingType(this ResourceManager resourceManager) + { + return ResourceManagers.TypeManagerCache.GetOrAdd(resourceManager); } - private class Disposer : IDisposable + /// Creates a clone of the passed in. Releases all resources on dispose. + internal sealed class ResourceManagerClone : IDisposable { - internal readonly ResourceSet ResourceSet; - private readonly ResourceManager resourceManager; + internal readonly ResourceManager ResourceManager; - public Disposer(ResourceManager resourceManager, ResourceSet resourceSet) + public ResourceManagerClone(ResourceManager source) { - Debug.Assert(resourceManager != null, "resourceManager == null"); - Debug.Assert(resourceSet != null, "resourceSet == null"); - this.resourceManager = resourceManager; - this.ResourceSet = resourceSet; + Debug.Assert(source != null, "resourceManager == null"); + var containingType = source.ContainingType(); + Debug.Assert(containingType != null, "containingType == null"); + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse want this check in release build + if (containingType != null) + { + this.ResourceManager = new ResourceManager(source.BaseName, containingType.Assembly); + } } public void Dispose() { - this.resourceManager.ReleaseAllResources(); - this.ResourceSet.Dispose(); + this.ResourceManager?.ReleaseAllResources(); } } } diff --git a/Gu.Localization/Internals/ResourceManagers.cs b/Gu.Localization/Internals/ResourceManagers.cs index 94f7c638..0637aa5a 100644 --- a/Gu.Localization/Internals/ResourceManagers.cs +++ b/Gu.Localization/Internals/ResourceManagers.cs @@ -2,21 +2,21 @@ namespace Gu.Localization { using System; using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; using System.Reflection; using System.Resources; /// A cache for resourcemanagers. - internal static class ResourceManagers + internal static class ResourceManagers { - private static readonly ConcurrentDictionary TypeManagerMap = new ConcurrentDictionary(); - /// Tries to get from cache or create a for /// Ex. typeof(Properties.Resources) /// The /// True if a could be created for internal static bool TryGetForType(Type resourcesType, out ResourceManager result) { - result = TypeManagerMap.GetOrAdd(resourcesType, CreateManagerForType); + result = TypeManagerCache.GetOrAdd(resourcesType, CreateManagerForTypeOrDefault); return result != null; } @@ -25,7 +25,7 @@ internal static bool TryGetForType(Type resourcesType, out ResourceManager resul /// A resource manager internal static ResourceManager ForType(Type resourcesType) { - var resourceManager = TypeManagerMap.GetOrAdd(resourcesType, CreateManagerForType); + var resourceManager = TypeManagerCache.GetOrAdd(resourcesType, CreateManagerForType); if (resourceManager == null) { var message = $"{nameof(resourcesType)} must have a property named ResourceManager of type ResourceManager"; @@ -35,15 +35,88 @@ internal static ResourceManager ForType(Type resourcesType) return resourceManager; } + private static ResourceManager CreateManagerForTypeOrDefault(Type type) + { + var property = GetResourceManagerProperty(type); + return property?.GetValue(null) as ResourceManager; + } + private static ResourceManager CreateManagerForType(Type type) { - var property = type.GetProperty(nameof(Properties.Resources.ResourceManager), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + var property = GetResourceManagerProperty(type); if (property == null || !typeof(ResourceManager).IsAssignableFrom(property.PropertyType)) { - return null; + var message = $"{nameof(type)} must have a property named ResourceManager of type ResourceManager"; + throw new ArgumentException(message); } return (ResourceManager)property.GetValue(null); } + + private static PropertyInfo GetResourceManagerProperty(this Type type) + { + var property = type.GetProperty( + nameof(Properties.Resources.ResourceManager), + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + return property; + } + + internal static class TypeManagerCache + { + private static readonly ConcurrentDictionary TypeManagerMap = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary ManagerTypeMap = new ConcurrentDictionary(ResourceManagerComparer.Default); + + internal static ResourceManager GetOrAdd(Type type, Func create) + { + var manager = TypeManagerMap.GetOrAdd(type, create); + if (manager != null) + { + ManagerTypeMap.TryAdd(manager, type); + } + + return manager; + } + + internal static Type GetOrAdd(ResourceManager resourceManager) + { + var type = ManagerTypeMap.GetOrAdd(resourceManager, ContainingType); + if (type != null) + { + TypeManagerMap.TryAdd(type, resourceManager); + } + + return type; + } + + private static Type ContainingType(ResourceManager resourceManager) + { + var resourcesType = AppDomain.CurrentDomain.GetAssemblies() + .Select(x => x.GetType(resourceManager.BaseName)) + .SingleOrDefault(x => x != null && + x.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static) + .Any(p => p.PropertyType == typeof(ResourceManager))); + return resourcesType; + } + + private class ResourceManagerComparer : IEqualityComparer + { + public static readonly ResourceManagerComparer Default = new ResourceManagerComparer(); + private static readonly StringComparer StringComparer = StringComparer.Ordinal; + + private ResourceManagerComparer() + { + } + + public bool Equals(ResourceManager x, ResourceManager y) + { + return StringComparer.Equals(x.BaseName, y.BaseName); + } + + public int GetHashCode(ResourceManager obj) + { + return StringComparer.GetHashCode(obj.BaseName); + } + } + } } } \ No newline at end of file diff --git a/Gu.Localization/Properties/Resources.Designer.cs b/Gu.Localization/Properties/Resources.Designer.cs index e7ab0a63..55e41e7b 100644 --- a/Gu.Localization/Properties/Resources.Designer.cs +++ b/Gu.Localization/Properties/Resources.Designer.cs @@ -60,6 +60,15 @@ internal Resources() { } } + /// + /// Looks up a localized string similar to {{{0} : {1}}}. + /// + public static string InvalidFormat { + get { + return ResourceManager.GetString("InvalidFormat", resourceCulture); + } + } + /// /// Looks up a localized string similar to ~{0}~. /// diff --git a/Gu.Localization/Properties/Resources.resx b/Gu.Localization/Properties/Resources.resx index 962ab73f..17bdc7ef 100644 --- a/Gu.Localization/Properties/Resources.resx +++ b/Gu.Localization/Properties/Resources.resx @@ -135,4 +135,7 @@ #{0}# + + {{{0} : {1}}} + \ No newline at end of file diff --git a/Gu.Localization/Translator.Parameters.cs b/Gu.Localization/Translator.Parameters.cs new file mode 100644 index 00000000..b1eab740 --- /dev/null +++ b/Gu.Localization/Translator.Parameters.cs @@ -0,0 +1,57 @@ +namespace Gu.Localization +{ + using System; + using System.Globalization; + using System.Resources; + + public static partial class Translator + { + /// + /// Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.SomeKey)); + /// This assumes that the resource is something like 'Value: {0}' i.e. having one format parameter. + /// + /// The containing translations. + /// The key in + /// The argument will be used as string.Format(format, ) + /// Specifies how to handle errors. + /// The key translated to the + internal static string Translate(ResourceManager resourceManager, string key, object arg, ErrorHandling errorHandling = ErrorHandling.Default) + { + return Translate(resourceManager, key, CurrentCulture, arg, errorHandling); + } + + /// + /// Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.SomeKey)); + /// This assumes that the resource is something like 'Value: {0}' i.e. having one format parameter. + /// + /// The containing translations. + /// The key in + /// The culture. + /// The argument will be used as string.Format(format, ) + /// Specifies how to handle errors. + /// The key translated to the + internal static string Translate(ResourceManager resourceManager, string key, CultureInfo culture, object arg, ErrorHandling errorHandling = ErrorHandling.Default) + { + string format; + if (!TryTranslateOrThrow(resourceManager, key, culture, errorHandling, out format)) + { + return format; + } + + if (ShouldThrow(errorHandling)) + { + Validate.Format(format, arg); + return string.Format(culture, format, arg); + } + + try + { + return string.Format(format, arg); + } + catch (Exception) + { + return string.Format(culture, Properties.Resources.InvalidFormat, format, arg); + } + } + } +} \ No newline at end of file diff --git a/Gu.Localization/Translator.cs b/Gu.Localization/Translator.cs index 5f2f3d45..f31cb3d6 100644 --- a/Gu.Localization/Translator.cs +++ b/Gu.Localization/Translator.cs @@ -10,7 +10,7 @@ using System.Threading; /// Class for translating resources - public static class Translator + public static partial class Translator { private static CultureInfo currentCulture = Thread.CurrentThread.CurrentUICulture; private static DirectoryInfo resourceDirectory = ResourceCultures.DefaultResourceDirectory(); @@ -124,47 +124,6 @@ public static string Translate( return result; } - /// - /// Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.SomeKey)); - /// This assumes that the resource is something like 'Value: {0}' i.e. having one format parameter. - /// - /// The containing translations. - /// The key in - /// The argument will be used as string.Format(format, ) - /// Specifies how to handle errors. - /// The key translated to the - internal static string Translate(ResourceManager resourceManager, string key, object arg, ErrorHandling errorHandling = ErrorHandling.Default) - { - return Translate(resourceManager, key, CurrentCulture, arg, errorHandling); - } - - /// - /// Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.SomeKey)); - /// This assumes that the resource is something like 'Value: {0}' i.e. having one format parameter. - /// - /// The containing translations. - /// The key in - /// The culture. - /// The argument will be used as string.Format(format, ) - /// Specifies how to handle errors. - /// The key translated to the - internal static string Translate(ResourceManager resourceManager, string key, CultureInfo culture, object arg, ErrorHandling errorHandling = ErrorHandling.Default) - { - string format; - if (!TryTranslateOrThrow(resourceManager, key, culture, errorHandling, out format)) - { - return format; - } - - if (ShouldThrow(errorHandling)) - { - Validate.Format(format, arg); - return string.Format(format, arg); - } - - throw new NotImplementedException("message"); - } - private static bool TryTranslateOrThrow( ResourceManager resourceManager, string key, diff --git a/Gu.Localization/Validate.Formats.cs b/Gu.Localization/Validate.Formats.cs index a9260617..0f0d8b0e 100644 --- a/Gu.Localization/Validate.Formats.cs +++ b/Gu.Localization/Validate.Formats.cs @@ -1,9 +1,18 @@ -namespace Gu.Localization +// ReSharper disable UnusedParameter.Global +namespace Gu.Localization { using System; + /// Methods for validating format resources. public partial class Validate { + /// + /// Call with Validate.IsValidFormat("First: {0:N}", 1.2); + /// Throws a if error(s) are found. + /// + /// Using generic to avoid boxing + /// The format string ex: 'First: {0:N} + /// The argument public static void Format(string format, T arg) { int count; @@ -13,14 +22,18 @@ public static void Format(string format, T arg) throw new FormatException($"Invalid format string: {format}."); } - throw new NotImplementedException("check if T is IFormattable if(anyItemHasFormat == true) "); - + // not sure if we should bother with checking individual format items here if (count != 1) { throw new FormatException($"Invalid format string: {format} for the single argument: {arg}."); } } + /// Call with Validate.IsValidFormat("First: {0:N}", 1.2); + /// Using generic to avoid boxing + /// The format string ex: 'First: {0:N} + /// The argument + /// True if is valid for one argument public static bool IsValidFormat(string format, T arg) { int count; @@ -30,8 +43,6 @@ public static bool IsValidFormat(string format, T arg) return false; } - throw new NotImplementedException("check if T is IFormattable if(anyItemHasFormat == true) "); - return count == 1; } } diff --git a/Gu.Localization/Validate.Translations.cs b/Gu.Localization/Validate.Translations.cs index 3ddcc72a..9bd8a5dc 100644 --- a/Gu.Localization/Validate.Translations.cs +++ b/Gu.Localization/Validate.Translations.cs @@ -45,28 +45,31 @@ public static TranslationErrors Translations(ResourceManager resourceManager) /// An with all errors found in public static TranslationErrors Translations(ResourceManager resourceManager, IEnumerable cultures) { - var resources = GetResources(resourceManager, cultures); - var keys = GetKeys(resourceManager); - Dictionary> errors = null; - foreach (var key in keys) + using (var clone = resourceManager.Clone()) { - var keyErrors = Translations(resources, key); - if (keyErrors.Count == 0) + var resources = GetResources(clone, cultures); + var keys = GetKeys(resourceManager); + Dictionary> errors = null; + foreach (var key in keys) { - continue; - } - - if (errors == null) - { - errors = new Dictionary>(); + var keyErrors = Translations(resources, key); + if (keyErrors.Count == 0) + { + continue; + } + + if (errors == null) + { + errors = new Dictionary>(); + } + + errors.Add(key, keyErrors); } - errors.Add(key, keyErrors); + return errors == null + ? TranslationErrors.Empty + : new TranslationErrors(errors); } - - return errors == null - ? TranslationErrors.Empty - : new TranslationErrors(errors); } /// @@ -97,27 +100,30 @@ public static TranslationErrors EnumTranslations(ResourceManager resourceMana public static TranslationErrors EnumTranslations(ResourceManager resourceManager, IEnumerable cultures) where T : struct, IComparable, IFormattable, IConvertible { - var resources = GetResources(resourceManager, cultures); - Dictionary> errors = null; - foreach (var key in Enum.GetNames(typeof(T))) + using (var clone = resourceManager.Clone()) { - var keyErrors = Translations(resources, key); - if (keyErrors.Count == 0) + var resources = GetResources(clone, cultures); + Dictionary> errors = null; + foreach (var key in Enum.GetNames(typeof(T))) { - continue; + var keyErrors = Translations(resources, key); + if (keyErrors.Count == 0) + { + continue; + } + + if (errors == null) + { + errors = new Dictionary>(); + } + + errors.Add(key, keyErrors); } - if (errors == null) - { - errors = new Dictionary>(); - } - - errors.Add(key, keyErrors); + return errors == null + ? TranslationErrors.Empty + : new TranslationErrors(errors); } - - return errors == null - ? TranslationErrors.Empty - : new TranslationErrors(errors); } /// @@ -152,8 +158,11 @@ public static IReadOnlyList Translations(ResourceManager resou /// A list with all errors for the key or an empty list if no errors. public static IReadOnlyList Translations(ResourceManager resourceManager, string key, IEnumerable cultures) { - var resources = GetResources(resourceManager, cultures); - return Translations(resources, key); + using (var clone = resourceManager.Clone()) + { + var resources = GetResources(clone, cultures); + return Translations(resources, key); + } } private static IReadOnlyList Translations(IReadOnlyDictionary resources, string key) @@ -214,9 +223,14 @@ private static IReadOnlyList GetKeys(ResourceManager resourceManager) .ToArray(); } - private static IReadOnlyDictionary GetResources(ResourceManager resourceManager, IEnumerable cultures) + private static IReadOnlyDictionary GetResources(ResourceManagerExt.ResourceManagerClone clone, IEnumerable cultures) { - return cultures.ToDictionary(c => c, c => resourceManager.GetResourceSet(c, true, false), CultureInfoComparer.Default); + if (clone == null || clone.ResourceManager == null) + { + return EmptyReadOnlyDictionary.Default; + } + + return cultures.ToDictionary(c => c, c => clone.ResourceManager.GetResourceSet(c, true, false), CultureInfoComparer.Default); } } } From cc0e9f200022c1dbcd03d6602562064be0b0eff5 Mon Sep 17 00:00:00 2001 From: Johan Larsson Date: Mon, 18 Apr 2016 12:06:44 +0200 Subject: [PATCH 3/6] Translate with one parameter. --- .../Internals/ResourceManagerExtTests.cs | 6 +++ .../Properties/Resources.Designer.cs | 15 +++++-- .../Properties/Resources.de.resx | 3 ++ .../Properties/Resources.en.resx | 5 ++- .../Properties/Resources.resx | 5 ++- .../Properties/Resources.sv.resx | 5 ++- .../TranslatorTests.Parameters.cs | 43 ++++++++++++++++--- .../ValidateTests.Translations.cs | 40 ++++++++--------- .../Properties/Resources.Designer.cs | 2 +- Gu.Localization/Properties/Resources.resx | 2 +- Gu.Localization/Translator.Parameters.cs | 5 +++ Gu.Localization/Validate.Formats.cs | 4 +- 12 files changed, 99 insertions(+), 36 deletions(-) diff --git a/Gu.Localization.Tests/Internals/ResourceManagerExtTests.cs b/Gu.Localization.Tests/Internals/ResourceManagerExtTests.cs index 98aa3d3b..f42995c7 100644 --- a/Gu.Localization.Tests/Internals/ResourceManagerExtTests.cs +++ b/Gu.Localization.Tests/Internals/ResourceManagerExtTests.cs @@ -6,6 +6,12 @@ public class ResourceManagerExtTests { + [OneTimeSetUp] + public void OneTimeSetUp() + { + Properties.Resources.ResourceManager.ReleaseAllResources(); + } + [TestCase(null, true)] [TestCase("sv", true)] [TestCase("it", false)] diff --git a/Gu.Localization.Tests/Properties/Resources.Designer.cs b/Gu.Localization.Tests/Properties/Resources.Designer.cs index d6f3da1c..d5bf0be9 100644 --- a/Gu.Localization.Tests/Properties/Resources.Designer.cs +++ b/Gu.Localization.Tests/Properties/Resources.Designer.cs @@ -87,6 +87,15 @@ internal static string first___0___second__1_ { } } + /// + /// Looks up a localized string similar to Value: {0}. + /// + internal static string InvalidFormat__0__ { + get { + return ResourceManager.GetString("InvalidFormat__0__", resourceCulture); + } + } + /// /// Looks up a localized string similar to So neutral. /// @@ -106,11 +115,11 @@ internal static string NoTranslation { } /// - /// Looks up a localized string similar to Value: {0}. + /// Looks up a localized string similar to Neutral: {0}. /// - internal static string Value___0_ { + internal static string ValidFormat__0__ { get { - return ResourceManager.GetString("Value___0_", resourceCulture); + return ResourceManager.GetString("ValidFormat__0__", resourceCulture); } } } diff --git a/Gu.Localization.Tests/Properties/Resources.de.resx b/Gu.Localization.Tests/Properties/Resources.de.resx index d2ab12f4..d303b407 100644 --- a/Gu.Localization.Tests/Properties/Resources.de.resx +++ b/Gu.Localization.Tests/Properties/Resources.de.resx @@ -123,4 +123,7 @@ zuerst: {0}, die zweite: {1} + + Wert: {0} + \ No newline at end of file diff --git a/Gu.Localization.Tests/Properties/Resources.en.resx b/Gu.Localization.Tests/Properties/Resources.en.resx index f275ea74..76c4a28d 100644 --- a/Gu.Localization.Tests/Properties/Resources.en.resx +++ b/Gu.Localization.Tests/Properties/Resources.en.resx @@ -126,7 +126,10 @@ first: {0}, second:{1} - + Value: {0} {1} + + Value: {0} + \ No newline at end of file diff --git a/Gu.Localization.Tests/Properties/Resources.resx b/Gu.Localization.Tests/Properties/Resources.resx index 9ddaafcc..35ab3a56 100644 --- a/Gu.Localization.Tests/Properties/Resources.resx +++ b/Gu.Localization.Tests/Properties/Resources.resx @@ -132,7 +132,10 @@ first: {0}, second:{1} - + Value: {0} + + Neutral: {0} + \ No newline at end of file diff --git a/Gu.Localization.Tests/Properties/Resources.sv.resx b/Gu.Localization.Tests/Properties/Resources.sv.resx index ffa0212c..86c7acfb 100644 --- a/Gu.Localization.Tests/Properties/Resources.sv.resx +++ b/Gu.Localization.Tests/Properties/Resources.sv.resx @@ -123,7 +123,10 @@ första: {0}, andra:{1} - + Värde: + + Värde: {0} + \ No newline at end of file diff --git a/Gu.Localization.Tests/TranslatorTests.Parameters.cs b/Gu.Localization.Tests/TranslatorTests.Parameters.cs index ebcf53a6..fd099e71 100644 --- a/Gu.Localization.Tests/TranslatorTests.Parameters.cs +++ b/Gu.Localization.Tests/TranslatorTests.Parameters.cs @@ -1,5 +1,6 @@ namespace Gu.Localization.Tests { + using System; using System.Globalization; using NUnit.Framework; @@ -11,20 +12,52 @@ public class Parameters [TestCase("en", 1, "Value: 1")] [TestCase("sv", 1, "Värde: 1")] [TestCase(null, 1, "Neutral: 1")] - public void TranslateOneParameterThrow(string cultureName, object arg, string expected) + public void TranslateOneParameterHappyPath(string cultureName, object arg, string expected) { var culture = cultureName != null ? CultureInfo.GetCultureInfo(cultureName) : CultureInfo.InvariantCulture; Translator.CurrentCulture = culture; - var actual = Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.Value___0_), arg); + var actual = Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.ValidFormat__0__), arg); + Assert.AreEqual(expected, actual); + + Translator.CurrentCulture = CultureInfo.GetCultureInfo("it"); + actual = Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.ValidFormat__0__), culture, arg); Assert.AreEqual(expected, actual); } - [Test] - public void TranslateOneParameterReturnInfo() + [TestCase("en", 1, "Invalid format string: \"Value: {0} {1}\" for the single argument: 1.")] + [TestCase("sv", 1, "Invalid format string: \"Värde: \" for the single argument: 1.")] + public void TranslateOneParameterThrow(string cultureName, object arg, string expected) { - Assert.Inconclusive(); + var culture = cultureName != null + ? CultureInfo.GetCultureInfo(cultureName) + : CultureInfo.InvariantCulture; + Translator.CurrentCulture = culture; + var actual = Assert.Throws(() => Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.InvalidFormat__0__), arg)); + Assert.AreEqual(expected, actual.Message); + + Translator.CurrentCulture = CultureInfo.GetCultureInfo("it"); + actual = Assert.Throws(() => Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.InvalidFormat__0__), culture, arg)); + Assert.AreEqual(expected, actual.Message); + } + + [TestCase("en", 1, "{\"Value: {0} {1}\" : 1}")] + [TestCase("sv", 1, "{\"Värde: \" : 1}")] + public void TranslateOneParameterReturnInfo(string cultureName, object arg, string expected) + { + var culture = cultureName != null + ? CultureInfo.GetCultureInfo(cultureName) + : CultureInfo.InvariantCulture; + Translator.ErrorHandling = ErrorHandling.ReturnErrorInfo; + Translator.CurrentCulture = culture; + var actual = Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.InvalidFormat__0__), arg); + Assert.AreEqual(expected, actual); + + Translator.ErrorHandling = ErrorHandling.Throw; + Translator.CurrentCulture = CultureInfo.GetCultureInfo("it"); + actual = Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.InvalidFormat__0__), culture, arg, ErrorHandling.ReturnErrorInfo); + Assert.AreEqual(expected, actual); } } } diff --git a/Gu.Localization.Tests/ValidateTests.Translations.cs b/Gu.Localization.Tests/ValidateTests.Translations.cs index a275e38a..2f1b7ee4 100644 --- a/Gu.Localization.Tests/ValidateTests.Translations.cs +++ b/Gu.Localization.Tests/ValidateTests.Translations.cs @@ -17,23 +17,22 @@ public void ForResourceManager() { var errors = Validate.Translations(Properties.Resources.ResourceManager); Assert.IsFalse(errors.IsEmpty); - CollectionAssert.AreEqual( - new[] { "NeutralOnly", "EnglishOnly", "NoTranslation", "Value___0_" }, - errors.Keys); + var expectedKeys = new[] { "InvalidFormat__0__", "NeutralOnly", "EnglishOnly", "NoTranslation" }; + CollectionAssert.AreEqual(expectedKeys, errors.Keys); var builder = new StringBuilder(); - builder.AppendLine("Key: NeutralOnly") - .AppendLine(" Missing for: { de, en, sv }") - .AppendLine("Key: EnglishOnly") - .AppendLine(" Missing for: { de, sv }") - .AppendLine("Key: NoTranslation") - .AppendLine(" Missing for: { de, en, sv }") - .AppendLine("Key: Value___0_") + builder.AppendLine("Key: InvalidFormat__0__") .AppendLine(" Has format errors, the formats are:") .AppendLine(" Value: {0}") .AppendLine(" null") .AppendLine(" Value: {0} {1}") .AppendLine(" Värde: ") - .AppendLine(" Missing for: { de }"); + .AppendLine(" Missing for: { de }") + .AppendLine("Key: NeutralOnly") + .AppendLine(" Missing for: { de, en, sv }") + .AppendLine("Key: EnglishOnly") + .AppendLine(" Missing for: { de, sv }") + .AppendLine("Key: NoTranslation") + .AppendLine(" Missing for: { de, en, sv }"); var expected = builder.ToString(); var actual = errors.ToString(" ", Environment.NewLine); Assert.AreEqual(expected, actual); @@ -45,20 +44,19 @@ public void TranslationsForResourceManagerExplicitCultures() var cultures = new[] { CultureInfo.GetCultureInfo("sv"), CultureInfo.GetCultureInfo("en") }; var errors = Validate.Translations(Properties.Resources.ResourceManager, cultures); Assert.IsFalse(errors.IsEmpty); - CollectionAssert.AreEqual( - new[] { "NeutralOnly", "EnglishOnly", "NoTranslation", "Value___0_" }, - errors.Keys); + var expectedKeys = new[] { "InvalidFormat__0__", "NeutralOnly", "EnglishOnly", "NoTranslation" }; + CollectionAssert.AreEqual(expectedKeys, errors.Keys); var builder = new StringBuilder(); - builder.AppendLine("Key: NeutralOnly") + builder.AppendLine("Key: InvalidFormat__0__") + .AppendLine(" Has format errors, the formats are:") + .AppendLine(" Värde: ") + .AppendLine(" Value: {0} {1}") + .AppendLine("Key: NeutralOnly") .AppendLine(" Missing for: { sv, en }") .AppendLine("Key: EnglishOnly") .AppendLine(" Missing for: { sv }") .AppendLine("Key: NoTranslation") - .AppendLine(" Missing for: { sv, en }") - .AppendLine("Key: Value___0_") - .AppendLine(" Has format errors, the formats are:") - .AppendLine(" Värde: ") - .AppendLine(" Value: {0} {1}"); + .AppendLine(" Missing for: { sv, en }"); var expected = builder.ToString(); var actual = errors.ToString(" ", Environment.NewLine); Assert.AreEqual(expected, actual); @@ -111,7 +109,7 @@ public void FormatsWithErrors() { var errors = Validate.Translations( Properties.Resources.ResourceManager, - Properties.Resources.Value___0_); + Properties.Resources.InvalidFormat__0__); CollectionAssert.IsNotEmpty(errors); } diff --git a/Gu.Localization/Properties/Resources.Designer.cs b/Gu.Localization/Properties/Resources.Designer.cs index 55e41e7b..04cd5b55 100644 --- a/Gu.Localization/Properties/Resources.Designer.cs +++ b/Gu.Localization/Properties/Resources.Designer.cs @@ -61,7 +61,7 @@ internal Resources() { } /// - /// Looks up a localized string similar to {{{0} : {1}}}. + /// Looks up a localized string similar to {{"{0}" : {1}}}. /// public static string InvalidFormat { get { diff --git a/Gu.Localization/Properties/Resources.resx b/Gu.Localization/Properties/Resources.resx index 17bdc7ef..7304fa62 100644 --- a/Gu.Localization/Properties/Resources.resx +++ b/Gu.Localization/Properties/Resources.resx @@ -136,6 +136,6 @@ #{0}# - {{{0} : {1}}} + {{"{0}" : {1}}} \ No newline at end of file diff --git a/Gu.Localization/Translator.Parameters.cs b/Gu.Localization/Translator.Parameters.cs index b1eab740..9281d2b8 100644 --- a/Gu.Localization/Translator.Parameters.cs +++ b/Gu.Localization/Translator.Parameters.cs @@ -44,6 +44,11 @@ internal static string Translate(ResourceManager resourceManager, string key, Cu return string.Format(culture, format, arg); } + if (!Validate.IsValidFormat(format, arg)) + { + return string.Format(culture, Properties.Resources.InvalidFormat, format, arg); + } + try { return string.Format(format, arg); diff --git a/Gu.Localization/Validate.Formats.cs b/Gu.Localization/Validate.Formats.cs index 0f0d8b0e..326aeb81 100644 --- a/Gu.Localization/Validate.Formats.cs +++ b/Gu.Localization/Validate.Formats.cs @@ -19,13 +19,13 @@ public static void Format(string format, T arg) bool? anyItemHasFormat; if (!FormatString.IsValidFormat(format, out count, out anyItemHasFormat)) { - throw new FormatException($"Invalid format string: {format}."); + throw new FormatException($"Invalid format string: \"{format}\"."); } // not sure if we should bother with checking individual format items here if (count != 1) { - throw new FormatException($"Invalid format string: {format} for the single argument: {arg}."); + throw new FormatException($"Invalid format string: \"{format}\" for the single argument: {arg}."); } } From be964980c2b500c993f8d4d1941a6a6831bd88f0 Mon Sep 17 00:00:00 2001 From: Johan Larsson Date: Mon, 18 Apr 2016 14:25:26 +0200 Subject: [PATCH 4/6] Two parameters. --- .../Gu.Localization.Tests.csproj | 3 +- .../Properties/Resources.Designer.cs | 9 ++ .../Properties/Resources.de.resx | 3 + .../Properties/Resources.en.resx | 5 +- .../Properties/Resources.resx | 3 + .../Properties/Resources.sv.resx | 3 + ...ers.cs => TranslatorTests.OneParameter.cs} | 30 +++--- .../TranslatorTests.TwoParameters.cs | 70 ++++++++++++ .../ValidateTests.Formats.cs | 10 ++ .../ValidateTests.Translations.cs | 54 ++-------- Gu.Localization/Translator.Parameters.cs | 83 +++++++++++++-- Gu.Localization/Validate.Formats.cs | 100 ++++++++++++++++-- 12 files changed, 296 insertions(+), 77 deletions(-) rename Gu.Localization.Tests/{TranslatorTests.Parameters.cs => TranslatorTests.OneParameter.cs} (70%) create mode 100644 Gu.Localization.Tests/TranslatorTests.TwoParameters.cs diff --git a/Gu.Localization.Tests/Gu.Localization.Tests.csproj b/Gu.Localization.Tests/Gu.Localization.Tests.csproj index 4a0028b8..3d27bb30 100644 --- a/Gu.Localization.Tests/Gu.Localization.Tests.csproj +++ b/Gu.Localization.Tests/Gu.Localization.Tests.csproj @@ -53,7 +53,8 @@ - + + diff --git a/Gu.Localization.Tests/Properties/Resources.Designer.cs b/Gu.Localization.Tests/Properties/Resources.Designer.cs index d5bf0be9..e0e9e2bd 100644 --- a/Gu.Localization.Tests/Properties/Resources.Designer.cs +++ b/Gu.Localization.Tests/Properties/Resources.Designer.cs @@ -122,5 +122,14 @@ internal static string ValidFormat__0__ { return ResourceManager.GetString("ValidFormat__0__", resourceCulture); } } + + /// + /// Looks up a localized string similar to Neutral first: {0}, second {1:F2}. + /// + internal static string ValidFormat__0__1__ { + get { + return ResourceManager.GetString("ValidFormat__0__1__", resourceCulture); + } + } } } diff --git a/Gu.Localization.Tests/Properties/Resources.de.resx b/Gu.Localization.Tests/Properties/Resources.de.resx index d303b407..1f72e8bc 100644 --- a/Gu.Localization.Tests/Properties/Resources.de.resx +++ b/Gu.Localization.Tests/Properties/Resources.de.resx @@ -126,4 +126,7 @@ Wert: {0} + + zuerst: {0}, die zweite: {1:F2} + \ No newline at end of file diff --git a/Gu.Localization.Tests/Properties/Resources.en.resx b/Gu.Localization.Tests/Properties/Resources.en.resx index 76c4a28d..f98a7220 100644 --- a/Gu.Localization.Tests/Properties/Resources.en.resx +++ b/Gu.Localization.Tests/Properties/Resources.en.resx @@ -127,9 +127,12 @@ first: {0}, second:{1} - Value: {0} {1} + Value: {0} {2} Value: {0} + + English first: {0}, second {1:F2} + \ No newline at end of file diff --git a/Gu.Localization.Tests/Properties/Resources.resx b/Gu.Localization.Tests/Properties/Resources.resx index 35ab3a56..8ff01265 100644 --- a/Gu.Localization.Tests/Properties/Resources.resx +++ b/Gu.Localization.Tests/Properties/Resources.resx @@ -138,4 +138,7 @@ Neutral: {0} + + Neutral first: {0}, second {1:F2} + \ No newline at end of file diff --git a/Gu.Localization.Tests/Properties/Resources.sv.resx b/Gu.Localization.Tests/Properties/Resources.sv.resx index 86c7acfb..c2f51754 100644 --- a/Gu.Localization.Tests/Properties/Resources.sv.resx +++ b/Gu.Localization.Tests/Properties/Resources.sv.resx @@ -129,4 +129,7 @@ Värde: {0} + + första: {0}, andra: {1:F2} + \ No newline at end of file diff --git a/Gu.Localization.Tests/TranslatorTests.Parameters.cs b/Gu.Localization.Tests/TranslatorTests.OneParameter.cs similarity index 70% rename from Gu.Localization.Tests/TranslatorTests.Parameters.cs rename to Gu.Localization.Tests/TranslatorTests.OneParameter.cs index fd099e71..e7428421 100644 --- a/Gu.Localization.Tests/TranslatorTests.Parameters.cs +++ b/Gu.Localization.Tests/TranslatorTests.OneParameter.cs @@ -7,56 +7,62 @@ public partial class TranslatorTests { - public class Parameters + public class OneParameter { [TestCase("en", 1, "Value: 1")] [TestCase("sv", 1, "Värde: 1")] [TestCase(null, 1, "Neutral: 1")] - public void TranslateOneParameterHappyPath(string cultureName, object arg, string expected) + public void HappyPath(string cultureName, object arg, string expected) { var culture = cultureName != null ? CultureInfo.GetCultureInfo(cultureName) : CultureInfo.InvariantCulture; + var key = nameof(Properties.Resources.ValidFormat__0__); + Translator.CurrentCulture = culture; - var actual = Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.ValidFormat__0__), arg); + var actual = Translator.Translate(Properties.Resources.ResourceManager, key, arg); Assert.AreEqual(expected, actual); Translator.CurrentCulture = CultureInfo.GetCultureInfo("it"); - actual = Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.ValidFormat__0__), culture, arg); + actual = Translator.Translate(Properties.Resources.ResourceManager, key, culture, arg); Assert.AreEqual(expected, actual); } - [TestCase("en", 1, "Invalid format string: \"Value: {0} {1}\" for the single argument: 1.")] + [TestCase("en", 1, "Invalid format string: \"Value: {0} {2}\".")] [TestCase("sv", 1, "Invalid format string: \"Värde: \" for the single argument: 1.")] - public void TranslateOneParameterThrow(string cultureName, object arg, string expected) + public void Throws(string cultureName, object arg, string expected) { var culture = cultureName != null ? CultureInfo.GetCultureInfo(cultureName) : CultureInfo.InvariantCulture; + var key = nameof(Properties.Resources.InvalidFormat__0__); + Translator.CurrentCulture = culture; - var actual = Assert.Throws(() => Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.InvalidFormat__0__), arg)); + var actual = Assert.Throws(() => Translator.Translate(Properties.Resources.ResourceManager, key, arg)); Assert.AreEqual(expected, actual.Message); Translator.CurrentCulture = CultureInfo.GetCultureInfo("it"); - actual = Assert.Throws(() => Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.InvalidFormat__0__), culture, arg)); + actual = Assert.Throws(() => Translator.Translate(Properties.Resources.ResourceManager, key, culture, arg)); Assert.AreEqual(expected, actual.Message); } - [TestCase("en", 1, "{\"Value: {0} {1}\" : 1}")] + [TestCase("en", 1, "{\"Value: {0} {2}\" : 1}")] [TestCase("sv", 1, "{\"Värde: \" : 1}")] - public void TranslateOneParameterReturnInfo(string cultureName, object arg, string expected) + public void ReturnsInfo(string cultureName, object arg, string expected) { var culture = cultureName != null ? CultureInfo.GetCultureInfo(cultureName) : CultureInfo.InvariantCulture; + var key = nameof(Properties.Resources.InvalidFormat__0__); + Translator.ErrorHandling = ErrorHandling.ReturnErrorInfo; Translator.CurrentCulture = culture; - var actual = Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.InvalidFormat__0__), arg); + var actual = Translator.Translate(Properties.Resources.ResourceManager, key, arg); Assert.AreEqual(expected, actual); Translator.ErrorHandling = ErrorHandling.Throw; Translator.CurrentCulture = CultureInfo.GetCultureInfo("it"); - actual = Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.InvalidFormat__0__), culture, arg, ErrorHandling.ReturnErrorInfo); + actual = Translator.Translate(Properties.Resources.ResourceManager, key, culture, arg, ErrorHandling.ReturnErrorInfo); Assert.AreEqual(expected, actual); } } diff --git a/Gu.Localization.Tests/TranslatorTests.TwoParameters.cs b/Gu.Localization.Tests/TranslatorTests.TwoParameters.cs new file mode 100644 index 00000000..e9cbd509 --- /dev/null +++ b/Gu.Localization.Tests/TranslatorTests.TwoParameters.cs @@ -0,0 +1,70 @@ +namespace Gu.Localization.Tests +{ + using System; + using System.Globalization; + + using NUnit.Framework; + + public partial class TranslatorTests + { + public class TwoParameters + { + [TestCase("en", 1, 2.0, "English first: 1, second 2.00")] + [TestCase("sv", 1, 2.0, "första: 1, andra: 2,00")] + [TestCase(null, 1, 2.0, "Neutral first: 1, second 2.00")] + public void HappyPath(string cultureName, object arg0, object arg1, string expected) + { + var culture = cultureName != null + ? CultureInfo.GetCultureInfo(cultureName) + : CultureInfo.InvariantCulture; + var key = nameof(Properties.Resources.ValidFormat__0__1__); + + Translator.CurrentCulture = culture; + var actual = Translator.Translate(Properties.Resources.ResourceManager, key, arg0, arg1); + Assert.AreEqual(expected, actual); + + Translator.CurrentCulture = CultureInfo.GetCultureInfo("it"); + actual = Translator.Translate(Properties.Resources.ResourceManager, key, culture, arg0, arg1); + Assert.AreEqual(expected, actual); + } + + [TestCase("en", 1, 2, "Invalid format string: \"Value: {0} {2}\".")] + [TestCase("sv", 1, 2, "Invalid format string: \"Värde: \" for the two arguments: 1, 2.")] + public void Throws(string cultureName, object arg0, object arg1, string expected) + { + var culture = cultureName != null + ? CultureInfo.GetCultureInfo(cultureName) + : CultureInfo.InvariantCulture; + var key = nameof(Properties.Resources.InvalidFormat__0__); + + Translator.CurrentCulture = culture; + var actual = Assert.Throws(() => Translator.Translate(Properties.Resources.ResourceManager, key, arg0, arg1)); + Assert.AreEqual(expected, actual.Message); + + Translator.CurrentCulture = CultureInfo.GetCultureInfo("it"); + actual = Assert.Throws(() => Translator.Translate(Properties.Resources.ResourceManager, key, culture, arg0, arg1)); + Assert.AreEqual(expected, actual.Message); + } + + [TestCase("en", 1, 2, "{\"Value: {0} {2}\" : 1, 2}")] + [TestCase("sv", 1, 2, "{\"Värde: \" : 1, 2}")] + public void ReturnsInfo(string cultureName, object arg0, object arg1, string expected) + { + var culture = cultureName != null + ? CultureInfo.GetCultureInfo(cultureName) + : CultureInfo.InvariantCulture; + var key = nameof(Properties.Resources.InvalidFormat__0__); + + Translator.ErrorHandling = ErrorHandling.ReturnErrorInfo; + Translator.CurrentCulture = culture; + var actual = Translator.Translate(Properties.Resources.ResourceManager, key, arg0, arg1); + Assert.AreEqual(expected, actual); + + Translator.ErrorHandling = ErrorHandling.Throw; + Translator.CurrentCulture = CultureInfo.GetCultureInfo("it"); + actual = Translator.Translate(Properties.Resources.ResourceManager, key, culture, arg0, arg1, ErrorHandling.ReturnErrorInfo); + Assert.AreEqual(expected, actual); + } + } + } +} \ No newline at end of file diff --git a/Gu.Localization.Tests/ValidateTests.Formats.cs b/Gu.Localization.Tests/ValidateTests.Formats.cs index ee05acef..ce587213 100644 --- a/Gu.Localization.Tests/ValidateTests.Formats.cs +++ b/Gu.Localization.Tests/ValidateTests.Formats.cs @@ -1,6 +1,7 @@ namespace Gu.Localization.Tests { using System; + using System.Globalization; using NUnit.Framework; @@ -8,6 +9,15 @@ public partial class ValidateTests { public class Formats { + [Test] + public void KeyWithErrors() + { + var errors = Validate.Translations( + Properties.Resources.ResourceManager, + Properties.Resources.InvalidFormat__0__); + CollectionAssert.IsNotEmpty(errors); + } + [TestCase("Hej")] [TestCase("First: {1}")] [TestCase("First: {0}, Second: {1}")] diff --git a/Gu.Localization.Tests/ValidateTests.Translations.cs b/Gu.Localization.Tests/ValidateTests.Translations.cs index 2f1b7ee4..bde24d9d 100644 --- a/Gu.Localization.Tests/ValidateTests.Translations.cs +++ b/Gu.Localization.Tests/ValidateTests.Translations.cs @@ -13,7 +13,7 @@ public partial class ValidateTests public class Translations { [Test] - public void ForResourceManager() + public void ResourceManager() { var errors = Validate.Translations(Properties.Resources.ResourceManager); Assert.IsFalse(errors.IsEmpty); @@ -24,7 +24,7 @@ public void ForResourceManager() .AppendLine(" Has format errors, the formats are:") .AppendLine(" Value: {0}") .AppendLine(" null") - .AppendLine(" Value: {0} {1}") + .AppendLine(" Value: {0} {2}") .AppendLine(" Värde: ") .AppendLine(" Missing for: { de }") .AppendLine("Key: NeutralOnly") @@ -39,7 +39,7 @@ public void ForResourceManager() } [Test] - public void TranslationsForResourceManagerExplicitCultures() + public void ResourceManagerExplicitCultures() { var cultures = new[] { CultureInfo.GetCultureInfo("sv"), CultureInfo.GetCultureInfo("en") }; var errors = Validate.Translations(Properties.Resources.ResourceManager, cultures); @@ -50,7 +50,7 @@ public void TranslationsForResourceManagerExplicitCultures() builder.AppendLine("Key: InvalidFormat__0__") .AppendLine(" Has format errors, the formats are:") .AppendLine(" Värde: ") - .AppendLine(" Value: {0} {1}") + .AppendLine(" Value: {0} {2}") .AppendLine("Key: NeutralOnly") .AppendLine(" Missing for: { sv, en }") .AppendLine("Key: EnglishOnly") @@ -83,42 +83,13 @@ public void EnumTranslationsExplicitCultures() Assert.AreEqual("Key: MissingTranslation Missing for: { sv, en } ", errors.ToString("", " ")); } - [Test] - public void FormatsHappyPath() - { - var errors = Validate.Translations( - Properties.Resources.ResourceManager, - nameof(Properties.Resources.first___0___second__1_)); - CollectionAssert.IsEmpty(errors); - - var cultures = new[] - { - CultureInfo.InvariantCulture, - CultureInfo.GetCultureInfo("en"), - CultureInfo.GetCultureInfo("sv") - }; - errors = Validate.Translations( - Properties.Resources.ResourceManager, - nameof(Properties.Resources.first___0___second__1_), - cultures); - CollectionAssert.IsEmpty(errors); - } - - [Test] - public void FormatsWithErrors() - { - var errors = Validate.Translations( - Properties.Resources.ResourceManager, - Properties.Resources.InvalidFormat__0__); - CollectionAssert.IsNotEmpty(errors); - } - - [Test] - public void TranslationsForKeyWhenNoErrors() + [TestCase(nameof(Properties.Resources.AllLanguages))] + [TestCase(nameof(Properties.Resources.ValidFormat__0__))] + [TestCase(nameof(Properties.Resources.ValidFormat__0__1__))] + public void KeyWhenNoErrors(string key) { - var errors = Validate.Translations( - Properties.Resources.ResourceManager, - nameof(Properties.Resources.AllLanguages)); + var resourceManager = Properties.Resources.ResourceManager; + var errors = Validate.Translations(resourceManager, key); CollectionAssert.IsEmpty(errors); var cultures = new[] @@ -127,10 +98,7 @@ public void TranslationsForKeyWhenNoErrors() CultureInfo.GetCultureInfo("en"), CultureInfo.GetCultureInfo("sv") }; - errors = Validate.Translations( - Properties.Resources.ResourceManager, - nameof(Properties.Resources.AllLanguages), - cultures); + errors = Validate.Translations(resourceManager, key, cultures); CollectionAssert.IsEmpty(errors); } } diff --git a/Gu.Localization/Translator.Parameters.cs b/Gu.Localization/Translator.Parameters.cs index 9281d2b8..e3383059 100644 --- a/Gu.Localization/Translator.Parameters.cs +++ b/Gu.Localization/Translator.Parameters.cs @@ -10,27 +10,29 @@ public static partial class Translator /// Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.SomeKey)); /// This assumes that the resource is something like 'Value: {0}' i.e. having one format parameter. /// + /// The type of generic to avoid boxing /// The containing translations. /// The key in - /// The argument will be used as string.Format(format, ) + /// The argument will be used as string.Format(format, ) /// Specifies how to handle errors. /// The key translated to the - internal static string Translate(ResourceManager resourceManager, string key, object arg, ErrorHandling errorHandling = ErrorHandling.Default) + public static string Translate(ResourceManager resourceManager, string key, T arg0, ErrorHandling errorHandling = ErrorHandling.Default) { - return Translate(resourceManager, key, CurrentCulture, arg, errorHandling); + return Translate(resourceManager, key, CurrentCulture, arg0, errorHandling); } /// /// Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.SomeKey)); /// This assumes that the resource is something like 'Value: {0}' i.e. having one format parameter. /// + /// The type of generic to avoid boxing /// The containing translations. /// The key in /// The culture. - /// The argument will be used as string.Format(format, ) + /// The argument will be used as string.Format(format, ) /// Specifies how to handle errors. /// The key translated to the - internal static string Translate(ResourceManager resourceManager, string key, CultureInfo culture, object arg, ErrorHandling errorHandling = ErrorHandling.Default) + public static string Translate(ResourceManager resourceManager, string key, CultureInfo culture, T arg0, ErrorHandling errorHandling = ErrorHandling.Default) { string format; if (!TryTranslateOrThrow(resourceManager, key, culture, errorHandling, out format)) @@ -40,22 +42,81 @@ internal static string Translate(ResourceManager resourceManager, string key, Cu if (ShouldThrow(errorHandling)) { - Validate.Format(format, arg); - return string.Format(culture, format, arg); + Validate.Format(format, arg0); + return string.Format(culture, format, arg0); } - if (!Validate.IsValidFormat(format, arg)) + if (!Validate.IsValidFormat(format, arg0)) { - return string.Format(culture, Properties.Resources.InvalidFormat, format, arg); + return string.Format(culture, Properties.Resources.InvalidFormat, format, arg0); } try { - return string.Format(format, arg); + return string.Format(format, arg0); } catch (Exception) { - return string.Format(culture, Properties.Resources.InvalidFormat, format, arg); + return string.Format(culture, Properties.Resources.InvalidFormat, format, arg0); + } + } + + /// + /// Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.SomeKey)); + /// This assumes that the resource is something like 'Value: {0}' i.e. having one format parameter. + /// + /// The type of generic to avoid boxing + /// The type of generic to avoid boxing + /// The containing translations. + /// The key in + /// The argument will be used as first arguyment in string.Format(culture, format, , ) + /// The argument will be used as second argument string.Format(culture, format, , ) + /// Specifies how to handle errors. + /// The key translated to the + public static string Translate(ResourceManager resourceManager, string key, T0 arg0, T1 arg1, ErrorHandling errorHandling = ErrorHandling.Default) + { + return Translate(resourceManager, key, CurrentCulture, arg0, arg1, errorHandling); + } + + /// + /// Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.SomeKey)); + /// This assumes that the resource is something like 'Value: {0}' i.e. having one format parameter. + /// + /// The type of generic to avoid boxing + /// The type of generic to avoid boxing + /// The containing translations. + /// The key in + /// The culture. + /// The argument will be used as first arguyment in string.Format(culture, format, , ) + /// The argument will be used as second argument string.Format(culture, format, , ) + /// Specifies how to handle errors. + /// The key translated to the + public static string Translate(ResourceManager resourceManager, string key, CultureInfo culture, T0 arg0, T1 arg1, ErrorHandling errorHandling = ErrorHandling.Default) + { + string format; + if (!TryTranslateOrThrow(resourceManager, key, culture, errorHandling, out format)) + { + return format; + } + + if (ShouldThrow(errorHandling)) + { + Validate.Format(format, arg0, arg1); + return string.Format(culture, format, arg0, arg1); + } + + if (!Validate.IsValidFormat(format, arg0, arg1)) + { + return string.Format(culture, Properties.Resources.InvalidFormat, format, string.Join(", ", arg0, arg1)); + } + + try + { + return string.Format(format, arg0, arg1); + } + catch (Exception) + { + return string.Format(culture, Properties.Resources.InvalidFormat, format, string.Join(", ", arg0, arg1)); } } } diff --git a/Gu.Localization/Validate.Formats.cs b/Gu.Localization/Validate.Formats.cs index 326aeb81..11925f21 100644 --- a/Gu.Localization/Validate.Formats.cs +++ b/Gu.Localization/Validate.Formats.cs @@ -10,10 +10,10 @@ public partial class Validate /// Call with Validate.IsValidFormat("First: {0:N}", 1.2); /// Throws a if error(s) are found. /// - /// Using generic to avoid boxing + /// The type of generic to avoid boxing /// The format string ex: 'First: {0:N} - /// The argument - public static void Format(string format, T arg) + /// The argument + public static void Format(string format, T0 arg0) { int count; bool? anyItemHasFormat; @@ -25,16 +25,98 @@ public static void Format(string format, T arg) // not sure if we should bother with checking individual format items here if (count != 1) { - throw new FormatException($"Invalid format string: \"{format}\" for the single argument: {arg}."); + throw new FormatException($"Invalid format string: \"{format}\" for the single argument: {arg0}."); + } + } + + /// + /// Call with Validate.IsValidFormat("First: {0:N}", 1.2); + /// Throws a if error(s) are found. + /// + /// The type of generic to avoid boxing + /// The type of generic to avoid boxing + /// The format string ex: 'First: {0:N} + /// The first argument. + /// The second argument. + public static void Format(string format, T0 arg0, T1 arg1) + { + int count; + bool? anyItemHasFormat; + if (!FormatString.IsValidFormat(format, out count, out anyItemHasFormat)) + { + throw new FormatException($"Invalid format string: \"{format}\"."); + } + + // not sure if we should bother with checking individual format items here + if (count != 2) + { + throw new FormatException($"Invalid format string: \"{format}\" for the two arguments: {arg0}, {arg1}."); + } + } + + /// + /// Call with Validate.IsValidFormat("First: {0:N}", 1, 2, 3..); + /// Throws a if error(s) are found. + /// + /// The format string ex: 'First: {0:N} + /// The arguments. + public static void Format(string format, params object[] args) + { + int count; + bool? anyItemHasFormat; + if (!FormatString.IsValidFormat(format, out count, out anyItemHasFormat)) + { + throw new FormatException($"Invalid format string: \"{format}\"."); + } + + if (args == null || args.Length == 0) + { + if (count == 0) + { + return; + } + + throw new FormatException($"Invalid format string: \"{format}\" when no arguments."); + } + + if (count != args.Length) + { + throw new FormatException($"Invalid format string: \"{format}\" for the arguments {{{string.Join(", ", args)}}}."); } } /// Call with Validate.IsValidFormat("First: {0:N}", 1.2); - /// Using generic to avoid boxing + /// The type of generic to avoid boxing + /// The format string ex: 'First: {0:N} + /// The argument + /// True if is valid for the argument + public static bool IsValidFormat(string format, T arg0) + { + return IsValidFormat(format, 1); + } + + /// Call with Validate.IsValidFormat("First: {0:N}, Second: {1}", 1, 2); + /// The type of generic to avoid boxing + /// The type of generic to avoid boxing + /// The format string ex: 'First: {0:N} + /// The first argument. + /// The second argument. + /// True if is valid for the two arguments and + public static bool IsValidFormat(string format, T0 arg0, T1 arg1) + { + return IsValidFormat(format, 2); + } + + /// Call with Validate.IsValidFormat("First: {0:N}, Second: {1}", 2); /// The format string ex: 'First: {0:N} - /// The argument - /// True if is valid for one argument - public static bool IsValidFormat(string format, T arg) + /// The arguments. + /// True if is valid for . + public static bool IsValidFormat(string format, params object[] args) + { + return IsValidFormat(format, args?.Length); + } + + private static bool IsValidFormat(string format, int argumentCount) { int count; bool? anyItemHasFormat; @@ -43,7 +125,7 @@ public static bool IsValidFormat(string format, T arg) return false; } - return count == 1; + return count == argumentCount; } } } From 8481fbd58fa2bb5c019eb0915b8cb99e8c06fddd Mon Sep 17 00:00:00 2001 From: Johan Larsson Date: Mon, 18 Apr 2016 15:07:14 +0200 Subject: [PATCH 5/6] Test Validate.IsValidFormat and Validate.Format for params --- .../ValidateTests.Formats.cs | 69 +++++++++++++------ .../ValidateTests.Translations.cs | 9 +++ Gu.Localization/Validate.Formats.cs | 4 +- 3 files changed, 60 insertions(+), 22 deletions(-) diff --git a/Gu.Localization.Tests/ValidateTests.Formats.cs b/Gu.Localization.Tests/ValidateTests.Formats.cs index ce587213..7556454e 100644 --- a/Gu.Localization.Tests/ValidateTests.Formats.cs +++ b/Gu.Localization.Tests/ValidateTests.Formats.cs @@ -1,40 +1,69 @@ namespace Gu.Localization.Tests { using System; - using System.Globalization; - using NUnit.Framework; public partial class ValidateTests { public class Formats { - [Test] - public void KeyWithErrors() + [TestCase("First: {0}")] + [TestCase("First: {0:N}")] + [TestCase("First: {0}, again: {0}")] + [TestCase("First: {0:F2}, Second: {0:F3}")] + public void OneArgumentHappyPath(string format) + { + Assert.DoesNotThrow(() => Validate.Format(format, 1)); + Assert.IsTrue(Validate.IsValidFormat(format, 1)); + } + + [TestCase("First: {0} Second: {1}")] + [TestCase("First: {0:N}, second: {1:F3}")] + [TestCase("First: {0}, again: {0:F2}, second: {1}")] + [TestCase("First: {1}, again: {0:F2}, second: {0}")] + public void TwoArgumentsHappyPath(string format) { - var errors = Validate.Translations( - Properties.Resources.ResourceManager, - Properties.Resources.InvalidFormat__0__); - CollectionAssert.IsNotEmpty(errors); + Assert.DoesNotThrow(() => Validate.Format(format, 1, 2)); + Assert.IsTrue(Validate.IsValidFormat(format, 1, 2)); } - [TestCase("Hej")] - [TestCase("First: {1}")] - [TestCase("First: {0}, Second: {1}")] - public void OneArgumentWithError(string format) + [TestCase("First: {0} second: {1}, third: {2}")] + [TestCase("First: {0:N}, second: {1:F3}, third: {2:G}")] + public void ParamsHappyPath(string format) { - Assert.Throws(() => Validate.Format(format, 1)); + Assert.DoesNotThrow(() => Validate.Format(format, 1, 2, 3)); + Assert.IsTrue(Validate.IsValidFormat(format, 1, 2, 3)); + } + + [TestCase("Hej", "Invalid format string: \"Hej\" for the single argument: 1.")] + [TestCase("First: {1}", "Invalid format string: \"First: {1}\".")] + [TestCase("First: {0}, Second: {1}", "Invalid format string: \"First: {0}, Second: {1}\" for the single argument: 1.")] + public void OneArgumentWithError(string format, string expected) + { + var exception = Assert.Throws(() => Validate.Format(format, 1)); + Assert.AreEqual(expected, exception.Message); Assert.IsFalse(Validate.IsValidFormat(format, 1)); } - [TestCase("First: {0}")] - [TestCase("First: {0:N}")] - [TestCase("First: {0}, Second: {0}")] - [TestCase("First: {0:F2}, Second: {0:F3}")] - public void OneArgumentHappyPath(string format) + [TestCase("Hej", "Invalid format string: \"Hej\" for the two arguments: 1, 2.")] + [TestCase("First: {1}", "Invalid format string: \"First: {1}\".")] + [TestCase("First: {0}, second: {1}, third: {2}", "Invalid format string: \"First: {0}, second: {1}, third: {2}\" for the two arguments: 1, 2.")] + public void TwoArgumentsWithError(string format, string expected) { - Assert.DoesNotThrow(() => Validate.Format(format, 1)); - Assert.IsTrue(Validate.IsValidFormat(format, 1)); + var exception = Assert.Throws(() => Validate.Format(format, 1, 2)); + Assert.AreEqual(expected, exception.Message); + Assert.IsFalse(Validate.IsValidFormat(format, 1, 2)); + } + + + [TestCase("Hej", "Invalid format string: \"Hej\" for the arguments: 1, 2, 3.")] + [TestCase("First: {1}", "Invalid format string: \"First: {1}\".")] + [TestCase("First: {0}, second: {1}, third: {2} {3}", "Invalid format string: \"First: {0}, second: {1}, third: {2} {3}\" for the arguments: 1, 2, 3.")] + public void ParamsWithError(string format, string expected) + { + var exception = Assert.Throws(() => Validate.Format(format, 1, 2, 3)); + Assert.AreEqual(expected, exception.Message); + Assert.IsFalse(Validate.IsValidFormat(format, 1, 2, 3)); } } } diff --git a/Gu.Localization.Tests/ValidateTests.Translations.cs b/Gu.Localization.Tests/ValidateTests.Translations.cs index bde24d9d..7c1c26d4 100644 --- a/Gu.Localization.Tests/ValidateTests.Translations.cs +++ b/Gu.Localization.Tests/ValidateTests.Translations.cs @@ -101,6 +101,15 @@ public void KeyWhenNoErrors(string key) errors = Validate.Translations(resourceManager, key, cultures); CollectionAssert.IsEmpty(errors); } + + [TestCase(nameof(Properties.Resources.InvalidFormat__0__))] + [TestCase(nameof(Properties.Resources.EnglishOnly))] + public void KeyWithErrors(string key) + { + var resourceManager = Properties.Resources.ResourceManager; + var errors = Validate.Translations(resourceManager, key); + CollectionAssert.IsNotEmpty(errors); + } } } } diff --git a/Gu.Localization/Validate.Formats.cs b/Gu.Localization/Validate.Formats.cs index 11925f21..51b1aecc 100644 --- a/Gu.Localization/Validate.Formats.cs +++ b/Gu.Localization/Validate.Formats.cs @@ -81,7 +81,7 @@ public static void Format(string format, params object[] args) if (count != args.Length) { - throw new FormatException($"Invalid format string: \"{format}\" for the arguments {{{string.Join(", ", args)}}}."); + throw new FormatException($"Invalid format string: \"{format}\" for the arguments: {string.Join(", ", args)}."); } } @@ -113,7 +113,7 @@ public static bool IsValidFormat(string format, T0 arg0, T1 arg1) /// True if is valid for . public static bool IsValidFormat(string format, params object[] args) { - return IsValidFormat(format, args?.Length); + return IsValidFormat(format, args?.Length ?? 0); } private static bool IsValidFormat(string format, int argumentCount) From 5c0dad336a82286164fe6fdc26153bad4f9f5044 Mon Sep 17 00:00:00 2001 From: Johan Larsson Date: Mon, 18 Apr 2016 16:02:04 +0200 Subject: [PATCH 6/6] done --- .../Gu.Localization.Tests.csproj | 1 + .../Properties/Resources.Designer.cs | 9 +++ .../Properties/Resources.de.resx | 3 + .../Properties/Resources.en.resx | 3 + .../Properties/Resources.resx | 3 + .../Properties/Resources.sv.resx | 3 + .../TranslatorTests.Params.cs | 70 +++++++++++++++++++ Gu.Localization/Translator.Parameters.cs | 62 ++++++++++++++++ 8 files changed, 154 insertions(+) create mode 100644 Gu.Localization.Tests/TranslatorTests.Params.cs diff --git a/Gu.Localization.Tests/Gu.Localization.Tests.csproj b/Gu.Localization.Tests/Gu.Localization.Tests.csproj index 3d27bb30..58ee216f 100644 --- a/Gu.Localization.Tests/Gu.Localization.Tests.csproj +++ b/Gu.Localization.Tests/Gu.Localization.Tests.csproj @@ -54,6 +54,7 @@ + diff --git a/Gu.Localization.Tests/Properties/Resources.Designer.cs b/Gu.Localization.Tests/Properties/Resources.Designer.cs index e0e9e2bd..1473cf8c 100644 --- a/Gu.Localization.Tests/Properties/Resources.Designer.cs +++ b/Gu.Localization.Tests/Properties/Resources.Designer.cs @@ -131,5 +131,14 @@ internal static string ValidFormat__0__1__ { return ResourceManager.GetString("ValidFormat__0__1__", resourceCulture); } } + + /// + /// Looks up a localized string similar to Neutral first: {0}, second {1:F2}, third: {2}. + /// + internal static string ValidFormat__0__1__2__ { + get { + return ResourceManager.GetString("ValidFormat__0__1__2__", resourceCulture); + } + } } } diff --git a/Gu.Localization.Tests/Properties/Resources.de.resx b/Gu.Localization.Tests/Properties/Resources.de.resx index 1f72e8bc..a987ebdc 100644 --- a/Gu.Localization.Tests/Properties/Resources.de.resx +++ b/Gu.Localization.Tests/Properties/Resources.de.resx @@ -129,4 +129,7 @@ zuerst: {0}, die zweite: {1:F2} + + zuerst: {0}, die zweite: {1:F2} meh {2} + \ No newline at end of file diff --git a/Gu.Localization.Tests/Properties/Resources.en.resx b/Gu.Localization.Tests/Properties/Resources.en.resx index f98a7220..6593b8e6 100644 --- a/Gu.Localization.Tests/Properties/Resources.en.resx +++ b/Gu.Localization.Tests/Properties/Resources.en.resx @@ -135,4 +135,7 @@ English first: {0}, second {1:F2} + + English first: {0}, second {1:F2}, third: {2} + \ No newline at end of file diff --git a/Gu.Localization.Tests/Properties/Resources.resx b/Gu.Localization.Tests/Properties/Resources.resx index 8ff01265..6e94805f 100644 --- a/Gu.Localization.Tests/Properties/Resources.resx +++ b/Gu.Localization.Tests/Properties/Resources.resx @@ -141,4 +141,7 @@ Neutral first: {0}, second {1:F2} + + Neutral first: {0}, second {1:F2}, third: {2} + \ No newline at end of file diff --git a/Gu.Localization.Tests/Properties/Resources.sv.resx b/Gu.Localization.Tests/Properties/Resources.sv.resx index c2f51754..c41f7e3b 100644 --- a/Gu.Localization.Tests/Properties/Resources.sv.resx +++ b/Gu.Localization.Tests/Properties/Resources.sv.resx @@ -132,4 +132,7 @@ första: {0}, andra: {1:F2} + + första: {0}, andra: {1:F2}, tredje: {2} + \ No newline at end of file diff --git a/Gu.Localization.Tests/TranslatorTests.Params.cs b/Gu.Localization.Tests/TranslatorTests.Params.cs new file mode 100644 index 00000000..7739fbf2 --- /dev/null +++ b/Gu.Localization.Tests/TranslatorTests.Params.cs @@ -0,0 +1,70 @@ +////namespace Gu.Localization.Tests +////{ +//// using System; +//// using System.Globalization; + +//// using NUnit.Framework; + +//// public partial class TranslatorTests +//// { +//// public class Params +//// { +//// [TestCase("en", 1, 2.0, 3, "English first: 1, second 2.00, third: 3")] +//// [TestCase("sv", 1, 2.0, 3, "första: 1, andra: 2,00, tredje: 3")] +//// [TestCase(null, 1, 2.0, 3, "Neutral first: 1, second 2.00, third: 3")] +//// public void HappyPath(string cultureName, object arg0, object arg1, object arg2, string expected) +//// { +//// var culture = cultureName != null +//// ? CultureInfo.GetCultureInfo(cultureName) +//// : CultureInfo.InvariantCulture; +//// var key = nameof(Properties.Resources.ValidFormat__0__1__2__); + +//// Translator.CurrentCulture = culture; +//// var actual = Translator.Translate(Properties.Resources.ResourceManager, key, arg0, arg1, arg2); +//// Assert.AreEqual(expected, actual); + +//// Translator.CurrentCulture = CultureInfo.GetCultureInfo("it"); +//// actual = Translator.Translate(Properties.Resources.ResourceManager, key, culture, arg0, arg1, arg2); +//// Assert.AreEqual(expected, actual); +//// } + +//// [TestCase("en", 1, 2, 3, "Invalid format string: \"Value: {0} {2}\".")] +//// [TestCase("sv", 1, 2,3, "Invalid format string: \"Värde: \" for the two arguments: 1, 2, 3.")] +//// public void Throws(string cultureName, object arg0, object arg1, object arg2, string expected) +//// { +//// var culture = cultureName != null +//// ? CultureInfo.GetCultureInfo(cultureName) +//// : CultureInfo.InvariantCulture; +//// var key = nameof(Properties.Resources.InvalidFormat__0__); + +//// Translator.CurrentCulture = culture; +//// var actual = Assert.Throws(() => Translator.Translate(Properties.Resources.ResourceManager, key, arg0, arg1, arg2)); +//// Assert.AreEqual(expected, actual.Message); + +//// Translator.CurrentCulture = CultureInfo.GetCultureInfo("it"); +//// actual = Assert.Throws(() => Translator.Translate(Properties.Resources.ResourceManager, key, culture, arg0, arg1, arg2)); +//// Assert.AreEqual(expected, actual.Message); +//// } + +//// [TestCase("en", 1, 2, 3, "{\"Value: {0} {2}\" : 1, 2, 3}")] +//// [TestCase("sv", 1, 2, 3, "{\"Värde: \" : 1, 2, 3}")] +//// public void ReturnsInfo(string cultureName, object arg0, object arg1, object arg2, string expected) +//// { +//// var culture = cultureName != null +//// ? CultureInfo.GetCultureInfo(cultureName) +//// : CultureInfo.InvariantCulture; +//// var key = nameof(Properties.Resources.InvalidFormat__0__); + +//// Translator.ErrorHandling = ErrorHandling.ReturnErrorInfo; +//// Translator.CurrentCulture = culture; +//// var actual = Translator.Translate(Properties.Resources.ResourceManager, key, arg0, arg1, arg2); +//// Assert.AreEqual(expected, actual); + +//// Translator.ErrorHandling = ErrorHandling.Throw; +//// Translator.CurrentCulture = CultureInfo.GetCultureInfo("it"); +//// actual = Translator.Translate(Properties.Resources.ResourceManager, key, culture, ErrorHandling.ReturnErrorInfo, arg0, arg1, arg2); +//// Assert.AreEqual(expected, actual); +//// } +//// } +//// } +////} \ No newline at end of file diff --git a/Gu.Localization/Translator.Parameters.cs b/Gu.Localization/Translator.Parameters.cs index e3383059..28ea9139 100644 --- a/Gu.Localization/Translator.Parameters.cs +++ b/Gu.Localization/Translator.Parameters.cs @@ -119,5 +119,67 @@ public static string Translate(ResourceManager resourceManager, string k return string.Format(culture, Properties.Resources.InvalidFormat, format, string.Join(", ", arg0, arg1)); } } + + /////// + /////// Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.SomeKey)); + /////// This assumes that the resource is something like 'Value: {0}' i.e. having one format parameter. + /////// + /////// The containing translations. + /////// The key in + /////// Specifies how to handle errors. + /////// The arguments will be used as first arguyment in string.Format(culture, format, ) + /////// The key translated to the + ////public static string Translate( + //// ResourceManager resourceManager, + //// string key, + //// ErrorHandling errorHandling = ErrorHandling.Default, + //// params object[] args) + ////{ + //// return Translate(resourceManager, key, CurrentCulture, errorHandling, args); + ////} + + /////// + /////// Translator.Translate(Properties.Resources.ResourceManager, nameof(Properties.Resources.SomeKey)); + /////// This assumes that the resource is something like 'Value: {0}' i.e. having one format parameter. + /////// + /////// The containing translations. + /////// The key in + /////// The culture. + /////// Specifies how to handle errors. + /////// The arguments will be used as first arguyment in string.Format(culture, format, ) + /////// The key translated to the + ////public static string Translate( + //// ResourceManager resourceManager, + //// string key, + //// CultureInfo culture, + //// ErrorHandling errorHandling = ErrorHandling.Default, + //// params object[] args) + ////{ + //// string format; + //// if (!TryTranslateOrThrow(resourceManager, key, culture, errorHandling, out format)) + //// { + //// return format; + //// } + + //// if (ShouldThrow(errorHandling)) + //// { + //// Validate.Format(format, args); + //// return string.Format(culture, format, args); + //// } + + //// if (!Validate.IsValidFormat(format, args)) + //// { + //// return string.Format(culture, Properties.Resources.InvalidFormat, format, string.Join(", ", args)); + //// } + + //// try + //// { + //// return string.Format(format, args); + //// } + //// catch (Exception) + //// { + //// return string.Format(culture, Properties.Resources.InvalidFormat, format, string.Join(", ", args)); + //// } + ////} } } \ No newline at end of file