localise CustomDialog exceptions

This commit is contained in:
bluepilledgreat 2025-03-11 18:29:20 +00:00
parent a4a82e1057
commit 94ed521d31
7 changed files with 386 additions and 36 deletions

View File

@ -0,0 +1,60 @@
using Bloxstrap.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bloxstrap.Exceptions
{
internal class CustomThemeException : Exception
{
/// <summary>
/// The exception message in English (for logging)
/// </summary>
public string EnglishMessage { get; } = null!;
public CustomThemeException(string translationString)
: base(Strings.ResourceManager.GetStringSafe(translationString))
{
EnglishMessage = Strings.ResourceManager.GetStringSafe(translationString, new CultureInfo("en-GB"));
}
public CustomThemeException(Exception innerException, string translationString)
: base(Strings.ResourceManager.GetStringSafe(translationString), innerException)
{
EnglishMessage = Strings.ResourceManager.GetStringSafe(translationString, new CultureInfo("en-GB"));
}
public CustomThemeException(string translationString, params object?[] args)
: base(string.Format(Strings.ResourceManager.GetStringSafe(translationString), args))
{
EnglishMessage = string.Format(Strings.ResourceManager.GetStringSafe(translationString, new CultureInfo("en-GB")), args);
}
public CustomThemeException(Exception innerException, string translationString, params object?[] args)
: base(string.Format(Strings.ResourceManager.GetStringSafe(translationString), args), innerException)
{
EnglishMessage = string.Format(Strings.ResourceManager.GetStringSafe(translationString, new CultureInfo("en-GB")), args);
}
public override string ToString()
{
StringBuilder sb = new StringBuilder(GetType().ToString());
if (!string.IsNullOrEmpty(Message))
sb.Append($": {Message}");
if (!string.IsNullOrEmpty(EnglishMessage) && Message != EnglishMessage)
sb.Append($" ({EnglishMessage})");
if (InnerException != null)
sb.Append($"\r\n ---> {InnerException}\r\n ");
if (StackTrace != null)
sb.Append($"\r\n{StackTrace}");
return sb.ToString();
}
}
}

View File

@ -847,6 +847,222 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Custom dialog has already been initialised.
/// </summary>
public static string CustomTheme_DialogAlreadyInitialised {
get {
return ResourceManager.GetString("CustomTheme.DialogAlreadyInitialised", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0}.{1} uses blacklisted scheme {2}.
/// </summary>
public static string CustomTheme_ElementAttributeBlacklistedUriScheme {
get {
return ResourceManager.GetString("CustomTheme.ElementAttributeBlacklistedUriScheme", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} has invalid {1}: {2}.
/// </summary>
public static string CustomTheme_ElementAttributeConversionError {
get {
return ResourceManager.GetString("CustomTheme.ElementAttributeConversionError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} {1} is not a valid {2}.
/// </summary>
public static string CustomTheme_ElementAttributeInvalidType {
get {
return ResourceManager.GetString("CustomTheme.ElementAttributeInvalidType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Element {0} is missing the {1} attribute.
/// </summary>
public static string CustomTheme_ElementAttributeMissing {
get {
return ResourceManager.GetString("CustomTheme.ElementAttributeMissing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0}.{1} is missing it&apos;s child.
/// </summary>
public static string CustomTheme_ElementAttributeMissingChild {
get {
return ResourceManager.GetString("CustomTheme.ElementAttributeMissingChild", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0}.{1} can only have one child.
/// </summary>
public static string CustomTheme_ElementAttributeMultipleChildren {
get {
return ResourceManager.GetString("CustomTheme.ElementAttributeMultipleChildren", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} can only have one {1} defined.
/// </summary>
public static string CustomTheme_ElementAttributeMultipleDefinitions {
get {
return ResourceManager.GetString("CustomTheme.ElementAttributeMultipleDefinitions", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} {1} must be larger than {2}.
/// </summary>
public static string CustomTheme_ElementAttributeMustBeLargerThanMin {
get {
return ResourceManager.GetString("CustomTheme.ElementAttributeMustBeLargerThanMin", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} {1} must be smaller than {2}.
/// </summary>
public static string CustomTheme_ElementAttributeMustBeSmallerThanMax {
get {
return ResourceManager.GetString("CustomTheme.ElementAttributeMustBeSmallerThanMax", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0}.{1} could not be parsed into a {2}.
/// </summary>
public static string CustomTheme_ElementAttributeParseError {
get {
return ResourceManager.GetString("CustomTheme.ElementAttributeParseError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0}.{1} {2} is null.
/// </summary>
public static string CustomTheme_ElementAttributeParseErrorNull {
get {
return ResourceManager.GetString("CustomTheme.ElementAttributeParseErrorNull", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} cannot have a child of {1}.
/// </summary>
public static string CustomTheme_ElementInvalidChild {
get {
return ResourceManager.GetString("CustomTheme.ElementInvalidChild", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} can only have one child.
/// </summary>
public static string CustomTheme_ElementMultipleChildren {
get {
return ResourceManager.GetString("CustomTheme.ElementMultipleChildren", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} failed to create {1}: {2}.
/// </summary>
public static string CustomTheme_ElementTypeCreationFailed {
get {
return ResourceManager.GetString("CustomTheme.ElementTypeCreationFailed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Theme XML root is not {0}.
/// </summary>
public static string CustomTheme_InvalidRoot {
get {
return ResourceManager.GetString("CustomTheme.InvalidRoot", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Custom bootstrappers can only have a maximum of {0} elements, got {1}..
/// </summary>
public static string CustomTheme_TooManyElements {
get {
return ResourceManager.GetString("CustomTheme.TooManyElements", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unknown element {0}.
/// </summary>
public static string CustomTheme_UnknownElement {
get {
return ResourceManager.GetString("CustomTheme.UnknownElement", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} Unknown {1} {2}.
/// </summary>
public static string CustomTheme_UnknownEnumValue {
get {
return ResourceManager.GetString("CustomTheme.UnknownEnumValue", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} version is not a number.
/// </summary>
public static string CustomTheme_VersionNotNumber {
get {
return ResourceManager.GetString("CustomTheme.VersionNotNumber", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} version {1} is not recognised.
/// </summary>
public static string CustomTheme_VersionNotRecognised {
get {
return ResourceManager.GetString("CustomTheme.VersionNotRecognised", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} version is not set.
/// </summary>
public static string CustomTheme_VersionNotSet {
get {
return ResourceManager.GetString("CustomTheme.VersionNotSet", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} version {1} is no longer supported.
/// </summary>
public static string CustomTheme_VersionNotSupported {
get {
return ResourceManager.GetString("CustomTheme.VersionNotSupported", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Failed to parse the theme file: {0}.
/// </summary>
public static string CustomTheme_XMLParseFailed {
get {
return ResourceManager.GetString("CustomTheme.XMLParseFailed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add Fast Flag.
/// </summary>

View File

@ -1294,4 +1294,76 @@ Please close any applications that may be using Roblox's files, and relaunch.</v
<data name="Enums.CustomThemeTemplate.Simple" xml:space="preserve">
<value>Simple</value>
</data>
<data name="CustomTheme.InvalidRoot" xml:space="preserve">
<value>Theme XML root is not {0}</value>
</data>
<data name="CustomTheme.DialogAlreadyInitialised" xml:space="preserve">
<value>Custom dialog has already been initialised</value>
</data>
<data name="CustomTheme.TooManyElements" xml:space="preserve">
<value>Custom bootstrappers can only have a maximum of {0} elements, got {1}.</value>
</data>
<data name="CustomTheme.VersionNotSet" xml:space="preserve">
<value>{0} version is not set</value>
</data>
<data name="CustomTheme.VersionNotNumber" xml:space="preserve">
<value>{0} version is not a number</value>
</data>
<data name="CustomTheme.VersionNotSupported" xml:space="preserve">
<value>{0} version {1} is no longer supported</value>
</data>
<data name="CustomTheme.VersionNotRecognised" xml:space="preserve">
<value>{0} version {1} is not recognised</value>
</data>
<data name="CustomTheme.ElementInvalidChild" xml:space="preserve">
<value>{0} cannot have a child of {1}</value>
</data>
<data name="CustomTheme.UnknownElement" xml:space="preserve">
<value>Unknown element {0}</value>
</data>
<data name="CustomTheme.XMLParseFailed" xml:space="preserve">
<value>Failed to parse the theme file: {0}</value>
</data>
<data name="CustomTheme.ElementAttributeConversionError" xml:space="preserve">
<value>{0} has invalid {1}: {2}</value>
</data>
<data name="CustomTheme.ElementAttributeMissing" xml:space="preserve">
<value>Element {0} is missing the {1} attribute</value>
</data>
<data name="CustomTheme.ElementAttributeInvalidType" xml:space="preserve">
<value>{0} {1} is not a valid {2}</value>
</data>
<data name="CustomTheme.ElementAttributeMustBeLargerThanMin" xml:space="preserve">
<value>{0} {1} must be larger than {2}</value>
</data>
<data name="CustomTheme.ElementAttributeMustBeSmallerThanMax" xml:space="preserve">
<value>{0} {1} must be smaller than {2}</value>
</data>
<data name="CustomTheme.UnknownEnumValue" xml:space="preserve">
<value>{0} Unknown {1} {2}</value>
</data>
<data name="CustomTheme.ElementAttributeMultipleDefinitions" xml:space="preserve">
<value>{0} can only have one {1} defined</value>
</data>
<data name="CustomTheme.ElementAttributeMultipleChildren" xml:space="preserve">
<value>{0}.{1} can only have one child</value>
</data>
<data name="CustomTheme.ElementMultipleChildren" xml:space="preserve">
<value>{0} can only have one child</value>
</data>
<data name="CustomTheme.ElementAttributeMissingChild" xml:space="preserve">
<value>{0}.{1} is missing it's child</value>
</data>
<data name="CustomTheme.ElementAttributeParseError" xml:space="preserve">
<value>{0}.{1} could not be parsed into a {2}</value>
</data>
<data name="CustomTheme.ElementAttributeParseErrorNull" xml:space="preserve">
<value>{0}.{1} {2} is null</value>
</data>
<data name="CustomTheme.ElementAttributeBlacklistedUriScheme" xml:space="preserve">
<value>{0}.{1} uses blacklisted scheme {2}</value>
</data>
<data name="CustomTheme.ElementTypeCreationFailed" xml:space="preserve">
<value>{0} failed to create {1}: {2}</value>
</data>
</root>

View File

@ -1,6 +1,7 @@
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;
using System.Xml;
using System.Xml.Linq;
namespace Bloxstrap.UI.Elements.Bootstrapper
@ -37,7 +38,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
}
catch (Exception ex)
{
throw new Exception($"{xmlElement.Name} has invalid {attributeName}: {ex.Message}", ex);
throw new CustomThemeException(ex, "CustomTheme.ElementAttributeConversionError", xmlElement.Name, attributeName, ex.Message);
}
}
@ -82,7 +83,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
}
catch (Exception ex)
{
throw new Exception($"{element.Name} has invalid {attributeName}: {ex.Message}", ex);
throw new CustomThemeException(ex, "CustomTheme.ElementAttributeConversionError", element.Name, attributeName, ex.Message);
}
}
}

View File

@ -59,11 +59,11 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
private static T HandleXml<T>(CustomDialog dialog, XElement xmlElement) where T : class
{
if (!_elementHandlerMap.ContainsKey(xmlElement.Name.ToString()))
throw new Exception($"Unknown element {xmlElement.Name}");
throw new CustomThemeException("CustomTheme.UnknownElement", xmlElement.Name);
var element = _elementHandlerMap[xmlElement.Name.ToString()](dialog, xmlElement);
if (element is not T)
throw new Exception($"{xmlElement.Parent!.Name} cannot have a child of {xmlElement.Name}");
throw new CustomThemeException("CustomTheme.ElementInvalidChild", xmlElement.Parent!.Name, xmlElement.Name);
return (T)element;
}
@ -81,34 +81,34 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
private static void AssertThemeVersion(string? versionStr)
{
if (string.IsNullOrEmpty(versionStr))
throw new Exception("BloxstrapCustomBootstrapper version is not set");
throw new CustomThemeException("CustomTheme.VersionNotSet", "BloxstrapCustomBootstrapper");
if (!uint.TryParse(versionStr, out uint version))
throw new Exception("BloxstrapCustomBootstrapper version is not a number");
throw new CustomThemeException("CustomTheme.VersionNotNumber", "BloxstrapCustomBootstrapper");
switch (version)
{
case Version:
break;
case 0: // Themes made between Oct 19, 2024 to Mar 11, 2025 (on the feature/custom-bootstrappers branch)
throw new Exception($"BloxstrapCustomBootstrapper version {version} is no longer supported");
throw new CustomThemeException("CustomTheme.VersionNotSupported", "BloxstrapCustomBootstrapper", version);
default:
throw new Exception($"BloxstrapCustomBootstrapper version {version} is not recognised");
throw new CustomThemeException("CustomTheme.VersionNotRecognised", "BloxstrapCustomBootstrapper", version);
}
}
private void HandleXmlBase(XElement xml)
{
if (_initialised)
throw new Exception("Custom dialog has already been initialised");
throw new CustomThemeException("CustomTheme.DialogAlreadyInitialised");
if (xml.Name != "BloxstrapCustomBootstrapper")
throw new Exception("XML root is not a BloxstrapCustomBootstrapper");
throw new CustomThemeException("CustomTheme.InvalidRoot", "BloxstrapCustomBootstrapper");
AssertThemeVersion(xml.Attribute("Version")?.Value);
if (xml.Descendants().Count() > MaxElements)
throw new Exception($"Custom bootstrappers can have a maximum of {MaxElements} elements");
throw new CustomThemeException("CustomTheme.TooManyElements", MaxElements, xml.Descendants().Count());
_initialised = true;
@ -134,7 +134,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
}
catch (Exception ex)
{
throw new Exception($"XML parse failed: {ex.Message}", ex);
throw new CustomThemeException(ex, "CustomTheme.XMLParseFailed", ex.Message);
}
HandleXmlBase(xml);

View File

@ -150,7 +150,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
}
catch (Exception ex)
{
throw new Exception($"ImageBrush Failed to create BitmapImage: {ex.Message}", ex);
throw new CustomThemeException(ex, "CustomTheme.ElementTypeCreationFailed", "Image", "BitmapImage", ex.Message);
}
imageBrush.ImageSource = bitmapImage;
@ -217,7 +217,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
var first = brushElement.FirstNode as XElement;
if (first == null)
throw new Exception($"{xmlElement.Name} {name} is missing the brush");
throw new CustomThemeException("CustomTheme.ElementAttributeMissingChild", xmlElement.Name, name);
var brush = HandleXml<Brush>(dialog, first);
uiElement.SetValue(dependencyProperty, brush);
@ -620,7 +620,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
}
catch (Exception ex)
{
throw new Exception($"Image Failed to create BitmapImage: {ex.Message}", ex);
throw new CustomThemeException(ex, "CustomTheme.ElementTypeCreationFailed", "Image", "BitmapImage", ex.Message);
}
image.Source = bitmapImage;
@ -693,7 +693,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
if (element.Name == "Grid.RowDefinitions")
{
if (rowsSet)
throw new Exception("Grid can only have one RowDefinitions defined");
throw new CustomThemeException("CustomTheme.ElementAttributeMultipleDefinitions", "Grid", "RowDefinitions");
rowsSet = true;
HandleXmlElement_Grid_RowDefinitions(grid, dialog, element);
@ -701,7 +701,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
else if (element.Name == "Grid.ColumnDefinitions")
{
if (columnsSet)
throw new Exception("Grid can only have one ColumnDefinitions defined");
throw new CustomThemeException("CustomTheme.ElementAttributeMultipleDefinitions", "Grid", "ColumnDefinitions");
columnsSet = true;
HandleXmlElement_Grid_ColumnDefinitions(grid, dialog, element);
@ -760,7 +760,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
if (children.Any())
{
if (children.Count() > 1)
throw new Exception("Border can only have one child");
throw new CustomThemeException("CustomTheme.ElementMultipleChildren", "Border");
border.Child = HandleXml<UIElement>(dialog, children.First());
}

View File

@ -26,7 +26,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
if (defaultValue != null)
return defaultValue;
throw new Exception($"Element {element.Name} is missing the {attributeName} attribute");
throw new CustomThemeException("CustomTheme.ElementAttributeMissing", element.Name, attributeName);
}
return attribute.Value.ToString();
@ -41,12 +41,12 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
if (defaultValue != null)
return (T)defaultValue;
throw new Exception($"Element {element.Name} is missing the {attributeName} attribute");
throw new CustomThemeException("CustomTheme.ElementAttributeMissing", element.Name, attributeName);
}
T? parsed = ConvertValue<T>(attribute.Value);
if (parsed == null)
throw new Exception($"{element.Name} {attributeName} is not a valid {typeof(T).Name}");
throw new CustomThemeException("CustomTheme.ElementAttributeInvalidType", element.Name, attributeName, typeof(T).Name);
return (T)parsed;
}
@ -63,7 +63,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
T? parsed = ConvertValue<T>(attribute.Value);
if (parsed == null)
throw new Exception($"{element.Name} {attributeName} is not a valid {typeof(T).Name}");
throw new CustomThemeException("CustomTheme.ElementAttributeInvalidType", element.Name, attributeName, typeof(T).Name);
return (T)parsed;
}
@ -71,17 +71,17 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
private static void ValidateXmlElement(string elementName, string attributeName, int value, int? min = null, int? max = null)
{
if (min != null && value < min)
throw new Exception($"{elementName} {attributeName} must be larger than {min}");
throw new CustomThemeException("CustomTheme.ElementAttributeMustBeLargerThanMin", elementName, attributeName, min);
if (max != null && value > max)
throw new Exception($"{elementName} {attributeName} must be smaller than {max}");
throw new CustomThemeException("CustomTheme.ElementAttributeMustBeSmallerThanMax", elementName, attributeName, max);
}
private static void ValidateXmlElement(string elementName, string attributeName, double value, double? min = null, double? max = null)
{
if (min != null && value < min)
throw new Exception($"{elementName} {attributeName} must be larger than {min}");
throw new CustomThemeException("CustomTheme.ElementAttributeMustBeLargerThanMin", elementName, attributeName, min);
if (max != null && value > max)
throw new Exception($"{elementName} {attributeName} must be smaller than {max}");
throw new CustomThemeException("CustomTheme.ElementAttributeMustBeSmallerThanMax", elementName, attributeName, max);
}
// You can't do numeric only generics in .NET 6. The feature is exclusive to .NET 7+.
@ -136,7 +136,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
return FontWeights.UltraBlack;
default:
throw new Exception($"{element.Name} Unknown FontWeight {value}");
throw new CustomThemeException("CustomTheme.UnknownEnumValue", element.Name, "FontWeight", value);
}
}
@ -158,7 +158,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
return FontStyles.Oblique;
default:
throw new Exception($"{element.Name} Unknown FontStyle {value}");
throw new CustomThemeException("CustomTheme.UnknownEnumValue", element.Name, "FontStyle", value);
}
}
@ -183,7 +183,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
return TextDecorations.Underline;
default:
throw new Exception($"{element.Name} Unknown TextDecorations {value}");
throw new CustomThemeException("CustomTheme.UnknownEnumValue", element.Name, "TextDecorations", value);
}
}
@ -205,6 +205,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
if (sourcePath == null)
return null;
// TODO: this is bad :(
return sourcePath.Replace("theme://", $"{dialog.ThemeDir}\\");
}
@ -218,13 +219,13 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
path = GetFullPath(dialog, path)!;
if (!Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out Uri? result))
throw new Exception($"{xmlElement.Name} failed to parse {name} as Uri");
throw new CustomThemeException("CustomTheme.ElementAttributeParseError", xmlElement.Name, name, "Uri");
if (result == null)
throw new Exception($"{xmlElement.Name} {name} Uri is null");
throw new CustomThemeException("CustomTheme.ElementAttributeParseErrorNull", xmlElement.Name, name, "Uri");
if (result.Scheme != "file")
throw new Exception($"{xmlElement.Name} {name} uses blacklisted scheme {result.Scheme}");
throw new CustomThemeException("CustomTheme.ElementAttributeBlacklistedUriScheme", xmlElement.Name, name, result.Scheme);
return new GetImageSourceDataResult { Uri = result };
}
@ -234,7 +235,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
var contentAttr = xmlElement.Attribute("Content");
var contentElement = xmlElement.Element($"{xmlElement.Name}.Content");
if (contentAttr != null && contentElement != null)
throw new Exception($"{xmlElement.Name} can only have one Content defined");
throw new CustomThemeException("CustomTheme.ElementAttributeMultipleDefinitions", xmlElement.Name, "Content");
if (contentAttr != null)
return GetTranslatedText(contentAttr.Value);
@ -244,11 +245,11 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
var children = contentElement.Elements();
if (children.Count() > 1)
throw new Exception($"{xmlElement.Name}.Content can only have one child");
throw new CustomThemeException("CustomTheme.ElementAttributeMultipleChildren", xmlElement.Name, "Content");
var first = contentElement.FirstNode as XElement;
if (first == null)
throw new Exception($"{xmlElement.Name} Content is missing the content");
throw new CustomThemeException("CustomTheme.ElementAttributeMissingChild", xmlElement.Name, "Content");
var uiElement = HandleXml<UIElement>(dialog, first);
return uiElement;
@ -262,7 +263,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
var children = effectElement.Elements();
if (children.Count() > 1)
throw new Exception($"{xmlElement.Name}.Effect can only have one child");
throw new CustomThemeException("CustomTheme.ElementAttributeMultipleChildren", xmlElement.Name, "Effect");
var child = children.FirstOrDefault();
if (child == null)