mirror of https://github.com/abpframework/abp.git
committed by
GitHub
231 changed files with 4150 additions and 1499 deletions
@ -0,0 +1,44 @@ |
|||
## Streamline Localization in Your ABP Project |
|||
|
|||
Making localization changes to an ABP project can be a daunting task, especially if you're dealing with multiple languages and translations. During development, it's easy to overlook some changes and that leads to inconsistencies across different languages. Fortunately, I have developed a tool that can help streamline the localization process and ensure consistency across different languages. |
|||
|
|||
The tool is a console application that uses JSON files to manage localization keys and their translations. It addresses three common scenarios that can arise during localization: |
|||
|
|||
|
|||
1. When the argument count of a key changes, it can be difficult to update the translations for all languages. My tool solves this problem by scanning all JSON files in the project folder and identifying any keys that have mismatched argument counts. It then offers two options to the user: delete the mismatched translations or export them as a JSON file for manual editing. |
|||
|
|||
2. When a new key is added to the project, forgetting to add its translations to all the other languages is easy. My tool helps to avoid this issue by scanning the default language's JSON file and identifying any keys that don't have translations in other languages. It then exports these keys as a JSON file that can be used to add missing translations. |
|||
|
|||
3. When a key's name is changed, it's important to update its translations in all the other languages. My tool makes this task simple by scanning all the JSON files in the project folder and updating any translations of the old key name with the new one. |
|||
|
|||
The tool also includes an export feature that allows users to modify translations outside of the application and import them back into the JSON files. |
|||
|
|||
## How it Helps |
|||
|
|||
With my Localization Key Synchronizer tool, you can perform complex localization changes more quickly and easily than by manually sifting through files and making changes one-by-one. This can save you significant time and effort, especially if you're working with a large number of languages or translations. |
|||
|
|||
## How it Works |
|||
|
|||
When you run the Localization Key Synchronizer tool, it presents you with three options: |
|||
|
|||
1. Find Asynchronous Keys |
|||
2. Apply Changes in the Exported File |
|||
3. Replace Keys |
|||
|
|||
If you select "Find Asynchronous Keys," the tool prompts you to enter the default language path. Once you've entered the path, the tool displays all of the JSON files in the same folder as a multi-select list. After selecting one or more files, you are asked whether you want to find keys that do not match the number of arguments, missing keys, or both. If you select "Missing Keys," the tool prompts you to enter the absolute path to export the missing keys. After you've entered the path, the export process starts, and the tool closes. |
|||
|
|||
 |
|||
|
|||
If you select "Apply Changes in the Exported File" at the main menu, the tool prompts you to enter the path to the exported file. After you've entered the path, the import process starts, and the tool closes. |
|||
|
|||
 |
|||
|
|||
If you select "Replace Keys," the tool prompts you to enter the localization folder path, the old key, the new key, and the JSON files to apply the changes to. Once you've entered all the required information and made your selections, the tool performs the replacements and closes. |
|||
|
|||
 |
|||
|
|||
## Conclusion |
|||
|
|||
If you're struggling to manage localization changes in an ABP project, give my Localization Key Synchronizer tool a try. It can help streamline your workflow and make the process much more manageable. You can find the tool on [GitHub](https://github.com/abpframework/abp/tree/dev/tools/localization-key-synchronizer). |
|||
|
|||
To use the tool, simply run the console application and follow the prompts. It's a user-friendly solution that helps to ensure localization consistency in your ABP project. Give it a try and let me know what you think! |
|||
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 2.7 MiB |
@ -0,0 +1,52 @@ |
|||
# Card Component |
|||
|
|||
The ABP Card Component is a wrapper component for the Bootstrap card class. |
|||
|
|||
## Usage |
|||
|
|||
ABP Card Component is a part of the `ThemeSharedModule` module. If you've imported that module into your module, you don't need to import it again. If not, first import it as shown below: |
|||
|
|||
```ts |
|||
// my-feature.module.ts |
|||
|
|||
import { ThemeSharedModule } from '@abp/ng.theme.shared'; |
|||
import { CardDemoComponent } from './chart-demo.component'; |
|||
|
|||
@NgModule({ |
|||
imports: [ |
|||
ThemeSharedModule , |
|||
// ... |
|||
], |
|||
declarations: [CardDemoComponent], |
|||
// ... |
|||
}) |
|||
export class MyFeatureModule {} |
|||
|
|||
``` |
|||
|
|||
Then, the `abp-card` component can be used. See the example below: |
|||
```ts |
|||
|
|||
// card-demo.component.ts |
|||
|
|||
import { Component } from '@angular/core'; |
|||
|
|||
@Component({ |
|||
selector: 'app-card-demo', |
|||
template: ` |
|||
<abp-card [cardStyle]="{width: '18rem'}"> |
|||
<abp-card-body> |
|||
<abp-card-title>Lorem Ipsum</abp-card-title> |
|||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla commodo condimentum ligula, sed varius nibh eleifend sit amet. Maecenas facilisis vel arcu nec maximus. |
|||
</abp-card-body> |
|||
</abp-card> |
|||
`, |
|||
}) |
|||
export class CardDemoComponent { } |
|||
``` |
|||
|
|||
See the result below: |
|||
|
|||
 |
|||
|
|||
As you can see in the example above, you can customize your card component's style with the `cardStyle` input. You can also add your custom classes with the `cardClass` input. |
|||
|
After Width: | Height: | Size: 26 KiB |
@ -0,0 +1,56 @@ |
|||
using System; |
|||
using IdentityModel.Client; |
|||
using Microsoft.AspNetCore.Authentication.OpenIdConnect; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
|
|||
namespace Microsoft.AspNetCore.Authentication.Cookies; |
|||
|
|||
public static class CookieAuthenticationOptionsExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Introspect access token on validating the principal.
|
|||
/// </summary>
|
|||
/// <param name="options"></param>
|
|||
/// <param name="oidcAuthenticationScheme"></param>
|
|||
/// <returns></returns>
|
|||
public static CookieAuthenticationOptions IntrospectAccessToken(this CookieAuthenticationOptions options, string oidcAuthenticationScheme = "oidc") |
|||
{ |
|||
var originalHandler = options.Events.OnValidatePrincipal; |
|||
options.Events.OnValidatePrincipal = async principalContext => |
|||
{ |
|||
originalHandler?.Invoke(principalContext); |
|||
|
|||
if (principalContext.Principal != null && principalContext.Principal.Identity != null && principalContext.Principal.Identity.IsAuthenticated) |
|||
{ |
|||
var accessToken = principalContext.Properties.GetTokenValue("access_token"); |
|||
if (!accessToken.IsNullOrWhiteSpace()) |
|||
{ |
|||
var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService<IOptionsMonitor<OpenIdConnectOptions>>().Get(oidcAuthenticationScheme); |
|||
if (openIdConnectOptions.Configuration == null && openIdConnectOptions.ConfigurationManager != null) |
|||
{ |
|||
openIdConnectOptions.Configuration = await openIdConnectOptions.ConfigurationManager.GetConfigurationAsync(principalContext.HttpContext.RequestAborted); |
|||
} |
|||
|
|||
var response = await openIdConnectOptions.Backchannel.IntrospectTokenAsync(new TokenIntrospectionRequest |
|||
{ |
|||
Address = openIdConnectOptions.Configuration?.IntrospectionEndpoint ?? openIdConnectOptions.Authority.EnsureEndsWith('/') + "connect/introspect", |
|||
ClientId = openIdConnectOptions.ClientId, |
|||
ClientSecret = openIdConnectOptions.ClientSecret, |
|||
Token = accessToken |
|||
}); |
|||
|
|||
if (response.IsActive) |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
|
|||
principalContext.RejectPrincipal(); |
|||
await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name); |
|||
} |
|||
}; |
|||
|
|||
return options; |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.AspNetCore.Components.Web.Configuration; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Components.WebAssembly.Configuration; |
|||
|
|||
[Dependency(ReplaceServices = true)] |
|||
public class BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService : |
|||
ICurrentApplicationConfigurationCacheResetService, |
|||
ITransientDependency |
|||
{ |
|||
private readonly WebAssemblyCachedApplicationConfigurationClient _webAssemblyCachedApplicationConfigurationClient; |
|||
|
|||
public BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService(WebAssemblyCachedApplicationConfigurationClient webAssemblyCachedApplicationConfigurationClient) |
|||
{ |
|||
_webAssemblyCachedApplicationConfigurationClient = webAssemblyCachedApplicationConfigurationClient; |
|||
} |
|||
|
|||
public async Task ResetAsync() |
|||
{ |
|||
await _webAssemblyCachedApplicationConfigurationClient.InitializeAsync(); |
|||
} |
|||
} |
|||
@ -1,16 +1,28 @@ |
|||
using System.Collections.Generic; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Core; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Select2; |
|||
|
|||
[DependsOn(typeof(CoreScriptContributor))] |
|||
public class Select2ScriptContributor : BundleContributor |
|||
{ |
|||
public const string PackageName = "select2"; |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
//TODO: Add select2.full.min.js or localize!
|
|||
//TODO: Add select2.full.min.js
|
|||
context.Files.AddIfNotContains("/libs/select2/js/select2.min.js"); |
|||
} |
|||
public override void ConfigureDynamicResources(BundleConfigurationContext context) |
|||
{ |
|||
var fileName = context.LazyServiceProvider.LazyGetRequiredService<IOptions<AbpLocalizationOptions>>().Value.GetCurrentUICultureLanguageFilesMap(PackageName); |
|||
var filePath = $"/libs/select2/js/i18n/{fileName}.js"; |
|||
if (context.FileProvider.GetFileInfo(filePath).Exists) |
|||
{ |
|||
context.Files.AddIfNotContains(filePath); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,94 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Newtonsoft.Json.Linq; |
|||
using Volo.Abp.Cli.Utils; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Cli.Commands.Services; |
|||
|
|||
public class SuiteAppSettingsService : ITransientDependency |
|||
{ |
|||
private const int DefaultPort = 3000; |
|||
|
|||
public CmdHelper CmdHelper { get; } |
|||
|
|||
public SuiteAppSettingsService(CmdHelper cmdHelper) |
|||
{ |
|||
CmdHelper = cmdHelper; |
|||
} |
|||
|
|||
public async Task<int> GetSuitePortAsync() |
|||
{ |
|||
return await GetSuitePortAsync(GetCurrentSuiteVersion()); |
|||
} |
|||
|
|||
public async Task<int> GetSuitePortAsync(string version) |
|||
{ |
|||
var filePath = GetFilePathOrNull(version); |
|||
|
|||
if (filePath == null) |
|||
{ |
|||
return DefaultPort; |
|||
} |
|||
|
|||
var content = File.ReadAllText(filePath); |
|||
|
|||
var contentAsJson = JObject.Parse(content); |
|||
|
|||
var url = contentAsJson["AbpSuite"]?["ApplicationUrl"]?.ToString(); |
|||
|
|||
if (url == null) |
|||
{ |
|||
return DefaultPort; |
|||
} |
|||
|
|||
return Convert.ToInt32(url.Split(":").Last()); |
|||
} |
|||
|
|||
private string GetFilePathOrNull(string version) |
|||
{ |
|||
if (version == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var path = Path.Combine( |
|||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), |
|||
".dotnet", |
|||
"tools", |
|||
".store", |
|||
"volo.abp.suite", |
|||
version, |
|||
"volo.abp.suite", |
|||
version, |
|||
"tools", |
|||
"net7.0", |
|||
"any", |
|||
"appsettings.json" |
|||
); |
|||
|
|||
if (!File.Exists(path)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return path; |
|||
} |
|||
|
|||
private string GetCurrentSuiteVersion() |
|||
{ |
|||
var dotnetToolList = CmdHelper.RunCmdAndGetOutput("dotnet tool list -g", out int exitCode); |
|||
|
|||
var suiteLine = dotnetToolList.Split(Environment.NewLine) |
|||
.FirstOrDefault(l => l.ToLower().StartsWith("volo.abp.suite ")); |
|||
|
|||
if (string.IsNullOrEmpty(suiteLine)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return suiteLine.Split(" ", StringSplitOptions.RemoveEmptyEntries)[1]; |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Http.Client.StaticProxying; |
|||
|
|||
public class AbpHttpClientStaticProxyingOptions |
|||
{ |
|||
public List<Type> BindingFromQueryTypes { get; } |
|||
|
|||
public AbpHttpClientStaticProxyingOptions() |
|||
{ |
|||
BindingFromQueryTypes = new List<Type>(); |
|||
} |
|||
} |
|||
@ -1,20 +1,21 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<PackageId>Volo.Abp.MultiLingualObject</PackageId> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.Localization\Volo.Abp.Localization.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<PackageId>Volo.Abp.MultiLingualObject</PackageId> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
<Nullable>enable</Nullable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.Localization\Volo.Abp.Localization.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
|
|||
@ -0,0 +1,67 @@ |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.UI.Navigation; |
|||
|
|||
public class ApplicationMenuGroup |
|||
{ |
|||
private string _displayName; |
|||
|
|||
/// <summary>
|
|||
/// Default <see cref="Order"/> value of a group item.
|
|||
/// </summary>
|
|||
public const int DefaultOrder = 1000; |
|||
|
|||
/// <summary>
|
|||
/// Unique name of the group in the application.
|
|||
/// </summary>
|
|||
[NotNull] |
|||
public string Name { get; } |
|||
|
|||
/// <summary>
|
|||
/// Display name of the group.
|
|||
/// </summary>
|
|||
[NotNull] |
|||
public string DisplayName { |
|||
get { return _displayName; } |
|||
set { |
|||
Check.NotNullOrWhiteSpace(value, nameof(value)); |
|||
_displayName = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Can be used to render the element with a specific Id for DOM selections.
|
|||
/// </summary>
|
|||
public string ElementId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The Display order of the group.
|
|||
/// Default value: 1000.
|
|||
/// </summary>
|
|||
public int Order { get; set; } |
|||
|
|||
public ApplicationMenuGroup( |
|||
[NotNull] string name, |
|||
[NotNull] string displayName, |
|||
string elementId = null, |
|||
int order = DefaultOrder) |
|||
{ |
|||
Check.NotNullOrWhiteSpace(name, nameof(name)); |
|||
Check.NotNullOrWhiteSpace(displayName, nameof(displayName)); |
|||
|
|||
Name = name; |
|||
DisplayName = displayName; |
|||
ElementId = elementId; |
|||
Order = order; |
|||
} |
|||
|
|||
private string GetDefaultElementId() |
|||
{ |
|||
return "MenuGroup_" + Name; |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return $"[ApplicationMenuGroup] Name = {Name}"; |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Volo.Abp.UI.Navigation; |
|||
|
|||
public class ApplicationMenuGroupList: List<ApplicationMenuGroup> |
|||
{ |
|||
public ApplicationMenuGroupList() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public ApplicationMenuGroupList(int capacity) |
|||
: base(capacity) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public ApplicationMenuGroupList(IEnumerable<ApplicationMenuGroup> collection) |
|||
: base(collection) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void Normalize() |
|||
{ |
|||
Order(); |
|||
} |
|||
|
|||
private void Order() |
|||
{ |
|||
var orderedItems = this.OrderBy(item => item.Order).ToArray(); |
|||
Clear(); |
|||
AddRange(orderedItems); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Volo.Abp.UI.Navigation; |
|||
|
|||
public interface IHasMenuGroups |
|||
{ |
|||
/// <summary>
|
|||
/// Menu groups.
|
|||
/// </summary>
|
|||
ApplicationMenuGroupList Groups { get; } |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue