Merge pull request #15275 from abpframework/auto-merge/rel-7-0/1628

Merge branch dev with rel-7.0
pull/15283/head
maliming 3 years ago committed by GitHub
commit 95996ea7fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -312,12 +312,14 @@ An enum properties is shown as combobox (select) in the create/edit forms:
Enum member name is shown on the table and forms by default. If you want to localize it, just create a new entry on your [localization](https://docs.abp.io/en/abp/latest/Localization) file:
````json
"UserType.SuperUser": "Super user"
"Enum:UserType.0": "Super user"
````
One of the following names can be used as the localization key:
* `Enum:UserType.0`
* `Enum:UserType.SuperUser`
* `UserType.0`
* `UserType.SuperUser`
* `SuperUser`

@ -199,7 +199,7 @@ This will add a new migration class to the project:
![bookstore-efcore-migration](./images/bookstore-efcore-migration.png)
> If you are using Visual Studio, you may want to use the `Add-Migration Created_Book_Entity -c BookStoreDbContext` and `Update-Database -Context BookStoreDbContext` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`Acme.BookStore.Web`{{else if UI=="BlazorServer"}}`Acme.BookStore.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`Acme.BookStore.HttpApi.Host`{{end}} is the startup project and `Acme.BookStore.EntityFrameworkCore` is the *Default Project* in PMC.
> If you are using Visual Studio, you may want to use the `Add-Migration Created_Book_Entity` and `Update-Database` commands in the *Package Manager Console (PMC)*. In this case, ensure that `Acme.BookStore.EntityFrameworkCore` is the startup project in Visual Studio and `Acme.BookStore.EntityFrameworkCore` is the *Default Project* in PMC.
{{end}}

@ -725,7 +725,7 @@ Book list page change is trivial. Open the `Pages/Books/Index.js` in the `Acme.B
title: l('Type'),
data: "type",
render: function (data) {
return l('Enum:BookType:' + data);
return l('Enum:BookType.' + data);
}
},
...

@ -132,22 +132,22 @@ Open the `en.json` (*the English translations*) file and change the content as s
"CreationTime": "Creation time",
"AreYouSure": "Are you sure?",
"AreYouSureToDelete": "Are you sure you want to delete this item?",
"Enum:BookType.Undefined": "Undefined",
"Enum:BookType.Adventure": "Adventure",
"Enum:BookType.Biography": "Biography",
"Enum:BookType.Dystopia": "Dystopia",
"Enum:BookType.Fantastic": "Fantastic",
"Enum:BookType.Horror": "Horror",
"Enum:BookType.Science": "Science",
"Enum:BookType.ScienceFiction": "Science fiction",
"Enum:BookType.Poetry": "Poetry"
"Enum:BookType.0": "Undefined",
"Enum:BookType.1": "Adventure",
"Enum:BookType.2": "Biography",
"Enum:BookType.3": "Dystopia",
"Enum:BookType.4": "Fantastic",
"Enum:BookType.5": "Horror",
"Enum:BookType.6": "Science",
"Enum:BookType.7": "Science fiction",
"Enum:BookType.8": "Poetry"
}
}
````
* Localization key names are arbitrary. You can set any name. We prefer some conventions for specific text types;
* Add `Menu:` prefix for menu items.
* Use `Enum:<enum-type>.<enum-name>` or `<enum-type>.<enum-name>` or `<enum-name>` naming convention to localize the enum members. When you do it like that, ABP can automatically localize the enums in some proper cases.
* Use `Enum:<enum-type>.<enum-value>` or `<enum-type>.<enum-value>` naming convention to localize the enum members. When you do it like that, ABP can automatically localize the enums in some proper cases.
If a text is not defined in the localization file, it **falls back** to the localization key (as ASP.NET Core's standard behavior).
@ -279,7 +279,7 @@ $(function () {
title: l('Type'),
data: "type",
render: function (data) {
return l('Enum:BookType:' + data);
return l('Enum:BookType.' + data);
}
},
{
@ -517,7 +517,7 @@ Open the `/src/app/book/book.component.html` and replace the content as shown be
<ngx-datatable-column [name]="'::Name' | abpLocalization" prop="name"></ngx-datatable-column>
<ngx-datatable-column [name]="'::Type' | abpLocalization" prop="type">
<ng-template let-row="row" ngx-datatable-cell-template>
{%{{{ '::Enum:BookType:' + row.type | abpLocalization }}}%}
{%{{{ '::Enum:BookType.' + row.type | abpLocalization }}}%}
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [name]="'::PublishDate' | abpLocalization" prop="publishDate">

@ -232,7 +232,7 @@ $(function () {
title: l('Type'),
data: "type",
render: function (data) {
return l('Enum:BookType:' + data);
return l('Enum:BookType.' + data);
}
},
{
@ -418,7 +418,7 @@ $(function () {
title: l('Type'),
data: "type",
render: function (data) {
return l('Enum:BookType:' + data);
return l('Enum:BookType.' + data);
}
},
{
@ -556,7 +556,7 @@ $(function () {
title: l('Type'),
data: "type",
render: function (data) {
return l('Enum:BookType:' + data);
return l('Enum:BookType.' + data);
}
},
{

@ -90,7 +90,7 @@ You can apply changes to the database using the following command, in the same c
dotnet ef database update
````
> If you are using Visual Studio, you may want to use `Add-Migration Added_Authors -c BookStoreDbContext` and `Update-Database -Context BookStoreDbContext` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`Acme.BookStore.Web`{{else if UI=="BlazorServer"}}`Acme.BookStore.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`Acme.BookStore.HttpApi.Host`{{end}} is the startup project and `Acme.BookStore.EntityFrameworkCore` is the *Default Project* in PMC.
> If you are using Visual Studio, you may want to use the `Add-Migration Created_Book_Entity` and `Update-Database` commands in the *Package Manager Console (PMC)*. In this case, ensure that `Acme.BookStore.EntityFrameworkCore` is the startup project in Visual Studio and `Acme.BookStore.EntityFrameworkCore` is the *Default Project* in PMC.
{{else if DB=="Mongo"}}

@ -201,6 +201,27 @@ In this case, you can add an entry to the localization file using the key `MyNam
> If you use the `[DisplayName]` but not add a corresponding entity to the localization file, then ABP Framework shows the given key as the field name, `MyNameKey` for this case. So, it provides a way to specify a hard coded display name even if you don't need to use the localization system.
### Enum Localization
Enum members are also automatically localized wherever possible. For example, when we added `<abp-select asp-for="Movie.Genre"/>` to the form (like we did in the *ABP Form Tag Helpers* section), ABP can automatically fill the localized names of Enum members. To enabled it, you should define the localized values in your localization JSON file. Example entries for the `Genre` Enum defined in the *ABP Form Tag Helpers* section:
````json
"Enum:Genre.0": "Classic movie",
"Enum:Genre.1": "Action movie",
"Enum:Genre.2": "Fiction",
"Enum:Genre.3": "Fantasy",
"Enum:Genre.4": "Animation/Cartoon"
````
You can use one of the following syntaxes for the localization keys:
* `Enum:<enum-type-name>.<enum-value>`
* `<enum-type-name>.<enum-value>`
> Remember that if you don't specify values for your Enum, the values will be ordered, starting from `0`.
> MVC tag helpers also support using Enum member names instead of values (so, you can define `"Enum:Genre.Action"` instead of `"Enum:Genre.1"`, for example), but it is not suggested. Because, when you serialize Enum properties to JSON and send to clients, default serializer uses Enum values instead of Enum names. So, the Enum name won't be available to clients, and it will be a problem if you want to use the same localization values on the client side.
## See Also
* [Server Side Validation](../../Validation.md)

@ -22,17 +22,20 @@ public class AbpSelectTagHelperService : AbpTagHelperService<AbpSelectTagHelper>
private readonly HtmlEncoder _encoder;
private readonly IAbpTagHelperLocalizer _tagHelperLocalizer;
private readonly IStringLocalizerFactory _stringLocalizerFactory;
private readonly IAbpEnumLocalizer _abpEnumLocalizer;
public AbpSelectTagHelperService(
IHtmlGenerator generator,
HtmlEncoder encoder,
IAbpTagHelperLocalizer tagHelperLocalizer,
IStringLocalizerFactory stringLocalizerFactory)
IStringLocalizerFactory stringLocalizerFactory,
IAbpEnumLocalizer abpEnumLocalizer)
{
_generator = generator;
_encoder = encoder;
_tagHelperLocalizer = tagHelperLocalizer;
_stringLocalizerFactory = stringLocalizerFactory;
_abpEnumLocalizer = abpEnumLocalizer;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
@ -264,22 +267,12 @@ public class AbpSelectTagHelperService : AbpTagHelperService<AbpSelectTagHelper>
foreach (var enumValue in enumType.GetEnumValues())
{
var memberName = enumType.GetEnumName(enumValue);
var localizedMemberName = AbpInternalLocalizationHelper.LocalizeWithFallback(
var localizedMemberName = _abpEnumLocalizer.GetString(enumType, enumValue,
new[]
{
containerLocalizer,
_stringLocalizerFactory.CreateDefaultOrNull()
},
new[]
{
$"Enum:{enumType.Name}.{memberName}",
$"{enumType.Name}.{memberName}",
memberName
},
memberName
);
containerLocalizer,
_stringLocalizerFactory.CreateDefaultOrNull()
});
selectItems.Add(new SelectListItem
{
Value = enumValue.ToString(),

@ -176,6 +176,7 @@ public abstract class AbpCrudPageBase<
{
[Inject] protected TAppService AppService { get; set; }
[Inject] protected IStringLocalizer<AbpUiResource> UiLocalizer { get; set; }
[Inject] public IAbpEnumLocalizer AbpEnumLocalizer { get; set; }
protected virtual int PageSize { get; } = LimitedResultRequestDto.DefaultMaxResultCount;
@ -626,7 +627,7 @@ public abstract class AbpCrudPageBase<
if (propertyInfo.Type.IsEnum)
{
column.ValueConverter = (val) =>
EnumHelper.GetLocalizedMemberName(propertyInfo.Type, val.As<ExtensibleObject>().ExtraProperties[propertyInfo.Name], StringLocalizerFactory);
AbpEnumLocalizer.GetString(propertyInfo.Type, val.As<ExtensibleObject>().ExtraProperties[propertyInfo.Name], new IStringLocalizer[]{ StringLocalizerFactory.CreateDefaultOrNull() });
}
yield return column;

@ -10,6 +10,7 @@ namespace Volo.Abp.BlazoriseUI.Components.ObjectExtending;
public static class EnumHelper
{
[Obsolete("Use IAbpEnumLocalizer instead.")]
public static string GetLocalizedMemberName(Type enumType, object value, IStringLocalizerFactory stringLocalizerFactory)
{
var memberName = enumType.GetEnumName(value);
@ -20,7 +21,9 @@ public static class EnumHelper
},
new[]
{
$"Enum:{enumType.Name}.{value}",
$"Enum:{enumType.Name}.{memberName}",
$"{enumType.Name}.{value}",
$"{enumType.Name}.{memberName}",
memberName
},

@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Components.Web;
using Volo.Abp.Data;
using Volo.Abp.Localization;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.BlazoriseUI.Components.ObjectExtending;
@ -17,6 +18,9 @@ public abstract class ExtensionPropertyComponentBase<TEntity, TResourceType> : O
[Inject]
public IStringLocalizerFactory StringLocalizerFactory { get; set; }
[Inject]
public IAbpEnumLocalizer AbpEnumLocalizer { get; set; }
[Inject]
public IValidationMessageLocalizerAttributeFinder ValidationMessageLocalizerAttributeFinder { get; set; }

@ -30,7 +30,7 @@ public partial class SelectExtensionProperty<TEntity, TResourceType>
selectItems.Add(new SelectItem<int>
{
Value = (int)enumValue,
Text = EnumHelper.GetLocalizedMemberName(PropertyInfo.Type, enumValue, StringLocalizerFactory)
Text = AbpEnumLocalizer.GetString(PropertyInfo.Type, enumValue, new []{ StringLocalizerFactory.CreateDefaultOrNull() })
});
}

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Localization;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Localization;
public class AbpEnumLocalizer : IAbpEnumLocalizer, ITransientDependency
{
protected readonly IStringLocalizerFactory StringLocalizerFactory;
public AbpEnumLocalizer(IStringLocalizerFactory stringLocalizerFactory)
{
StringLocalizerFactory = stringLocalizerFactory;
}
public virtual string GetString(Type enumType, object enumValue)
{
return GetStringInternal(enumType, enumValue, StringLocalizerFactory.CreateDefaultOrNull());
}
public virtual string GetString(Type enumType, object enumValue, params IStringLocalizer[] specifyLocalizers)
{
return GetStringInternal(enumType, enumValue, specifyLocalizers);
}
protected virtual string GetStringInternal(Type enumType, object enumValue, params IStringLocalizer[] specifyLocalizers)
{
var memberName = enumType.GetEnumName(enumValue);
var localizedString = GetStringOrNull(
specifyLocalizers,
new[]
{
$"Enum:{enumType.Name}.{enumValue}",
$"Enum:{enumType.Name}.{memberName}",
$"{enumType.Name}.{enumValue}",
$"{enumType.Name}.{memberName}",
memberName
}
);
return localizedString ?? memberName;
}
protected virtual string GetStringOrNull(IStringLocalizer[] localizers, IEnumerable<string> keys)
{
foreach (var key in keys)
{
foreach (var l in localizers)
{
if (l == null)
{
continue;
}
var localizedString = l[key];
if (!localizedString.ResourceNotFound)
{
return localizedString.Value;
}
}
}
return null;
}
}

@ -0,0 +1,19 @@
using System;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.Localization;
public static class AbpEnumLocalizerExtensions
{
public static string GetString<TEnum>(this IAbpEnumLocalizer abpEnumLocalizer, object enumValue)
where TEnum : Enum
{
return abpEnumLocalizer.GetString(typeof(TEnum), enumValue);
}
public static string GetString<TEnum>(this IAbpEnumLocalizer abpEnumLocalizer, object enumValue, IStringLocalizer[] specifyLocalizers)
where TEnum : Enum
{
return abpEnumLocalizer.GetString(typeof(TEnum), enumValue, specifyLocalizers);
}
}

@ -1,4 +1,5 @@
using Microsoft.Extensions.Localization;
using System;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.Localization;
@ -22,6 +23,7 @@ public static class AbpInternalLocalizationHelper
/// Return value if none of the localizers has none of the keys.
/// </param>
/// <returns></returns>
[Obsolete("Use IAbpEnumLocalizer instead.")]
public static string LocalizeWithFallback(
IStringLocalizer[] localizers,
string[] keys,

@ -0,0 +1,11 @@
using System;
using Microsoft.Extensions.Localization;
namespace Volo.Abp.Localization;
public interface IAbpEnumLocalizer
{
string GetString(Type enumType, object enumValue);
string GetString(Type enumType, object enumValue, IStringLocalizer[] specifyLocalizers);
}

@ -0,0 +1,47 @@
using Microsoft.Extensions.Localization;
using Shouldly;
using Volo.Abp.Localization.TestResources.Base.Validation;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.Localization;
public class AbpEnumLocalizer_Tests : AbpIntegratedTest<AbpLocalizationTestModule>
{
private readonly IAbpEnumLocalizer _enumLocalizer;
public AbpEnumLocalizer_Tests()
{
_enumLocalizer = GetRequiredService<IAbpEnumLocalizer>();
}
[Fact]
public void GetString_Test()
{
using (CultureHelper.Use("en"))
{
_enumLocalizer.GetString<BookType>(BookType.Undefined).ShouldBe("Undefined");
_enumLocalizer.GetString<BookType>(BookType.Adventure).ShouldBe("Adventure");
_enumLocalizer.GetString<BookType>(0).ShouldBe("Undefined with value 0");
_enumLocalizer.GetString<BookType>(1).ShouldBe("Adventure with value 1");
_enumLocalizer.GetString<BookType>(BookType.Biography).ShouldBe("Biography");
var specifyLocalizer = new[]
{
GetRequiredService<IStringLocalizerFactory>().Create<LocalizationTestValidationResource>()
};
_enumLocalizer.GetString<BookType>(BookType.Undefined, specifyLocalizer).ShouldBe("Undefined from ValidationResource");
_enumLocalizer.GetString<BookType>(BookType.Adventure, specifyLocalizer).ShouldBe("Adventure from ValidationResource");
_enumLocalizer.GetString<BookType>(0, specifyLocalizer).ShouldBe("Undefined with value 0 from ValidationResource");
_enumLocalizer.GetString<BookType>(1, specifyLocalizer).ShouldBe("Adventure with value 1 from ValidationResource");
_enumLocalizer.GetString<BookType>(BookType.Biography, specifyLocalizer).ShouldBe("Biography from ValidationResource");
}
}
}
enum BookType
{
Undefined,
Adventure,
Biography,
}

@ -19,6 +19,8 @@ public class AbpLocalizationTestModule : AbpModule
Configure<AbpLocalizationOptions>(options =>
{
options.DefaultResourceType = typeof(LocalizationTestResource);
options.Resources
.Add<LocalizationTestValidationResource>("en")
.AddVirtualJson("/Volo/Abp/Localization/TestResources/Base/Validation");
@ -35,8 +37,8 @@ public class AbpLocalizationTestModule : AbpModule
options.Resources
.Get<LocalizationTestResource>()
.AddVirtualJson("/Volo/Abp/Localization/TestResources/SourceExt");
options.GlobalContributors.Add<TestExternalLocalizationContributor>();
});
}
}
}

@ -2,6 +2,11 @@
"culture": "en",
"texts": {
"ThisFieldIsRequired": "This field is required",
"MaxLenghtErrorMessage": "This field can be maximum of '{0}' chars"
"MaxLenghtErrorMessage": "This field can be maximum of '{0}' chars",
"Enum:BookType.Undefined": "Undefined from ValidationResource",
"Enum:BookType.0": "Undefined with value 0 from ValidationResource",
"BookType.Adventure": "Adventure from ValidationResource",
"BookType.1": "Adventure with value 1 from ValidationResource",
"Biography": "Biography from ValidationResource"
}
}
}

@ -6,6 +6,11 @@
"CarPlural": "Cars",
"MaxLenghtErrorMessage": "This field's length can be maximum of '{0}' chars",
"Universe": "Universe",
"FortyTwo": "Forty Two"
"FortyTwo": "Forty Two",
"Enum:BookType.Undefined": "Undefined",
"Enum:BookType.0": "Undefined with value 0",
"BookType.Adventure": "Adventure",
"BookType.1": "Adventure with value 1",
"Biography": "Biography"
}
}
}

Loading…
Cancel
Save