mirror of https://github.com/abpframework/abp.git
committed by
GitHub
35 changed files with 585 additions and 87 deletions
@ -0,0 +1,82 @@ |
|||
# ABP.IO Platform 10.1 Final Has Been Released! |
|||
|
|||
We are glad to announce that [ABP](https://abp.io/) 10.1 stable version has been released. |
|||
|
|||
## What's New With Version 10.1? |
|||
|
|||
All the new features were explained in detail in the [10.1 RC Announcement Post](https://abp.io/community/announcements/announcing-abp-10-1-release-candidate-cyqui19d), so there is no need to review them again. You can check it out for more details. |
|||
|
|||
## Getting Started with 10.1 |
|||
|
|||
### How to Upgrade an Existing Solution |
|||
|
|||
You can upgrade your existing solutions with either ABP Studio or ABP CLI. In the following sections, both approaches are explained: |
|||
|
|||
### Upgrading via ABP Studio |
|||
|
|||
If you are already using the ABP Studio, you can upgrade it to the latest version. ABP Studio periodically checks for updates in the background, and when a new version of ABP Studio is available, you will be notified through a modal. Then, you can update it by confirming the opened modal. See [the documentation](https://abp.io/docs/latest/studio/installation#upgrading) for more info. |
|||
|
|||
After upgrading the ABP Studio, then you can open your solution in the application, and simply click the **Upgrade ABP Packages** action button to instantly upgrade your solution: |
|||
|
|||
 |
|||
|
|||
### Upgrading via ABP CLI |
|||
|
|||
Alternatively, you can upgrade your existing solution via ABP CLI. First, you need to install the ABP CLI or upgrade it to the latest version. |
|||
|
|||
If you haven't installed it yet, you can run the following command: |
|||
|
|||
```bash |
|||
dotnet tool install -g Volo.Abp.Studio.Cli |
|||
``` |
|||
|
|||
Or to update the existing CLI, you can run the following command: |
|||
|
|||
```bash |
|||
dotnet tool update -g Volo.Abp.Studio.Cli |
|||
``` |
|||
|
|||
After installing/updating the ABP CLI, you can use the [`update` command](https://abp.io/docs/latest/CLI#update) to update all the ABP related NuGet and NPM packages in your solution as follows: |
|||
|
|||
```bash |
|||
abp update |
|||
``` |
|||
|
|||
You can run this command in the root folder of your solution to update all ABP related packages. |
|||
|
|||
## Migration Guides |
|||
|
|||
There are a few breaking changes in this version that may affect your application. Please read the migration guide carefully, if you are upgrading from v10.0 or earlier versions: [ABP Version 10.1 Migration Guide](https://abp.io/docs/latest/release-info/migration-guides/abp-10-1) |
|||
|
|||
## Community News |
|||
|
|||
### New ABP Community Articles |
|||
|
|||
As always, exciting articles have been contributed by the ABP community. I will highlight some of them here: |
|||
|
|||
* [Enis Necipoğlu](https://abp.io/community/members/enisn): |
|||
* [ABP Framework's Hidden Magic: Things That Just Work Without You Knowing](https://abp.io/community/articles/hidden-magic-things-that-just-work-without-you-knowing-vw6osmyt) |
|||
* [Implementing Multiple Global Query Filters with Entity Framework Core](https://abp.io/community/articles/implementing-multiple-global-query-filters-with-entity-ugnsmf6i) |
|||
* [Suhaib Mousa](https://abp.io/community/members/suhaib-mousa): |
|||
* [.NET 11 Preview 1 Highlights: Faster Runtime, Smarter JIT, and AI-Ready Improvements](https://abp.io/community/articles/dotnet-11-preview-1-highlights-hspp3o5x) |
|||
* [TOON vs JSON for LLM Prompts in ABP: Token-Efficient Structured Context](https://abp.io/community/articles/toon-vs-json-b4rn2avd) |
|||
* [Fahri Gedik](https://abp.io/community/members/fahrigedik): |
|||
* [Building a Multi-Agent AI System with A2A, MCP, and ADK in .NET](https://abp.io/community/articles/building-a-multiagent-ai-system-with-a2a-mcp-iefdehyx) |
|||
* [Async Chain of Persistence Pattern: Designing for Failure in Event-Driven Systems](https://abp.io/community/articles/async-chain-of-persistence-pattern-wzjuy4gl) |
|||
* [Alper Ebiçoğlu](https://abp.io/community/members/alper): |
|||
* [NDC London 2026: From a Developer's Perspective and My Personal Notes about AI](https://abp.io/community/articles/ndc-london-2026-a-.net-conf-from-a-developers-perspective-07wp50yl) |
|||
* [Which Open-Source PDF Libraries Are Recently Popular? A Data-Driven Look At PDF Topic](https://abp.io/community/articles/which-opensource-pdf-libraries-are-recently-popular-a-g68q78it) |
|||
* [Engincan Veske](https://abp.io/community/members/EngincanV): |
|||
* [Stop Spam and Toxic Users in Your App with AI](https://abp.io/community/articles/stop-spam-and-toxic-users-in-your-app-with-ai-3i0xxh0y) |
|||
* [Liming Ma](https://abp.io/community/members/maliming): |
|||
* [How AI Is Changing Developers](https://abp.io/community/articles/how-ai-is-changing-developers-e8y4a85f) |
|||
* [Tarık Özdemir](https://abp.io/community/members/mtozdemir): |
|||
* [JetBrains State of Developer Ecosystem Report 2025 — Key Insights](https://abp.io/community/articles/jetbrains-state-of-developer-ecosystem-report-2025-key-z0638q5e) |
|||
* [Adnan Ali](https://abp.io/community/members/adnanaldaim): |
|||
* [Integrating AI into ABP.IO Applications: The Complete Guide to Volo.Abp.AI and AI Management Module](https://abp.io/community/articles/integrating-ai-into-abp.io-applications-the-complete-guide-jc9fbjq0) |
|||
|
|||
Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/create) to the ABP Community. |
|||
|
|||
## About the Next Version |
|||
|
|||
The next feature version will be 10.2. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. |
|||
|
After Width: | Height: | Size: 471 KiB |
|
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,162 @@ |
|||
```json |
|||
//[doc-seo] |
|||
{ |
|||
"Description": "Combine backend and UI localizations in Angular: use JSON files to override or extend server-sidtexts with the same key format and abpLocalization pipe." |
|||
} |
|||
``` |
|||
|
|||
# Hybrid Localization |
|||
|
|||
Hybrid localization lets you combine **backend localizations** (from the ABP server) with **UI localizations** (JSON files in your Angular app). UI values take priority over backend values for the same key, so you can override or extend server-side texts without changing the backend. |
|||
|
|||
## How It Works |
|||
|
|||
- **Backend localizations**: Loaded from the server (e.g. `ApplicationLocalizationResourceDto`). Keys use the format `ResourceName::Key`. |
|||
- **UI localizations**: Loaded from static JSON files under your app's assets (e.g. `/assets/localization/en.json`). The same key format `ResourceName::Key` is used. |
|||
- **Priority**: When a key exists in both backend and UI, the **UI value is used** (UI overrides backend). |
|||
|
|||
The existing `abpLocalization` pipe and localization APIs work unchanged; they resolve keys from the merged set (backend + UI), with UI winning on conflicts. |
|||
|
|||
## Configuration |
|||
|
|||
Enable hybrid localization in your app config via `provideAbpCore` and `withOptions`: |
|||
|
|||
```typescript |
|||
// app.config.ts |
|||
import { provideAbpCore, withOptions } from "@abp/ng.core"; |
|||
|
|||
export const appConfig: ApplicationConfig = { |
|||
providers: [ |
|||
provideAbpCore( |
|||
withOptions({ |
|||
// ...other options |
|||
uiLocalization: { |
|||
enabled: true, |
|||
basePath: "/assets/localization", // optional; default is '/assets/localization' |
|||
}, |
|||
}), |
|||
), |
|||
// ... |
|||
], |
|||
}; |
|||
``` |
|||
|
|||
| Option | Description | Default | |
|||
| ---------- | ---------------------------------------------------------------------------- | ------------------------ | |
|||
| `enabled` | Turn on UI localization loading from `{basePath}/{culture}.json`. | — | |
|||
| `basePath` | Base path for JSON files. Files are loaded from `{basePath}/{culture}.json`. | `'/assets/localization'` | |
|||
|
|||
When `enabled` is `true`, the app loads a JSON file for the current language (e.g. `en`, `tr`) whenever the user changes language. Loaded data is merged with backend localizations (UI overrides backend for the same key). |
|||
|
|||
## UI Localization File Format |
|||
|
|||
Place one JSON file per culture under your `basePath`. File name must be `{culture}.json` (e.g. `en.json`, `tr.json`). |
|||
|
|||
Structure: **resource name → key → value**. |
|||
|
|||
```json |
|||
{ |
|||
"MyProjectName": { |
|||
"Welcome": "Welcome from UI (en.json)", |
|||
"CustomKey": "This is a UI-only localization", |
|||
"TestMessage": "UI localization is working!" |
|||
}, |
|||
"AbpAccount": { |
|||
"Login": "Sign In (UI Override)" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
- Top-level keys are **resource names** (e.g. `MyProjectName`, `AbpAccount`). |
|||
- Nested keys are **localization keys**; values are the display strings for that culture. |
|||
|
|||
In templates you keep using the same key format: `ResourceName::Key`. |
|||
|
|||
## Using in Templates |
|||
|
|||
Use the `abpLocalization` pipe as usual. Keys can come from backend only, UI only, or both (UI wins): |
|||
|
|||
```html |
|||
<!-- Backend (if available) or UI --> |
|||
<p>{%{{ 'MyProjectName::Welcome' | abpLocalization }}%}</p> |
|||
|
|||
<!-- UI-only key (from /assets/localization/{{ culture }}.json) --> |
|||
<p>{%{{ 'MyProjectName::CustomKey' | abpLocalization }}%}</p> |
|||
|
|||
<!-- Backend key overridden by UI --> |
|||
<p>{%{{ 'AbpAccount::Login' | abpLocalization }}%}</p> |
|||
``` |
|||
|
|||
No template changes are needed; only the configuration and the JSON files. |
|||
|
|||
## UILocalizationService |
|||
|
|||
The `UILocalizationService` (`@abp/ng.core`) manages UI localizations and merges them with backend data. |
|||
|
|||
### Get loaded UI data |
|||
|
|||
To inspect what was loaded from the UI JSON files (e.g. for debugging or display): |
|||
|
|||
```typescript |
|||
import { UILocalizationService, SessionStateService } from "@abp/ng.core"; |
|||
|
|||
export class MyComponent { |
|||
private uiLocalizationService = inject(UILocalizationService); |
|||
private sessionState = inject(SessionStateService); |
|||
|
|||
currentLanguage$ = this.sessionState.getLanguage$(); |
|||
|
|||
ngOnInit() { |
|||
// All loaded UI resources for current language |
|||
const loaded = this.uiLocalizationService.getLoadedLocalizations(); |
|||
// Or for a specific culture |
|||
const loadedEn = this.uiLocalizationService.getLoadedLocalizations("en"); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
`getLoadedLocalizations(culture?: string)` returns an object of the form `{ [resourceName: string]: Record<string, string> }` for the given culture (or current language if omitted). |
|||
|
|||
### Add translations at runtime |
|||
|
|||
You can also add or merge UI translations programmatically (e.g. from another source or lazy-loaded module): |
|||
|
|||
```typescript |
|||
this.uiLocalizationService.addAngularLocalizeLocalization( |
|||
'en', // culture |
|||
'MyProjectName', // resource name |
|||
{ MyKey: 'My value' }, // key-value map |
|||
); |
|||
``` |
|||
|
|||
This merges into the existing UI localizations and is taken into account by the `abpLocalization` pipe with the same UI-over-backend priority. |
|||
|
|||
## Example: Dev App |
|||
|
|||
The ABP dev app demonstrates hybrid localization: |
|||
|
|||
1. **Config** (`app.config.ts`): |
|||
|
|||
```typescript |
|||
uiLocalization: { |
|||
enabled: true, |
|||
basePath: '/assets/localization', |
|||
}, |
|||
``` |
|||
|
|||
2. **Files**: `src/assets/localization/en.json` and `src/assets/localization/tr.json` with the structure shown above. |
|||
|
|||
3. **Component** (`localization-test.component.ts`): Uses `abpLocalization` for backend keys, UI-only keys, and overrides; and uses `UILocalizationService.getLoadedLocalizations()` to show loaded UI data. |
|||
|
|||
See `apps/dev-app/src/app/localization-test/localization-test.component.ts` and `apps/dev-app/src/assets/localization/*.json` in the repository for the full example. |
|||
|
|||
## Summary |
|||
|
|||
| Topic | Description | |
|||
|------------------|-------------| |
|||
| **Purpose** | Combine backend and UI localizations; UI overrides backend for the same key. | |
|||
| **Config** | `provideAbpCore(withOptions({ uiLocalization: { enabled: true, basePath?: string } }))`. | |
|||
| **File location**| `{basePath}/{culture}.json` (e.g. `/assets/localization/en.json`). | |
|||
| **JSON format** | `{ "ResourceName": { "Key": "Value", ... }, ... }`. | |
|||
| **Template usage** | Same as before: `{%{{ 'ResourceName::Key' \| abpLocalization }}%}`. | |
|||
| **API** | `UILocalizationService`: `getLoadedLocalizations(culture?)`, `addAngularLocalizeLocalization(culture, resourceName, translations)`. | |
|||
@ -1,12 +1,9 @@ |
|||
// This file is part of AbpApplicationConfigurationClientProxy, you can customize it here
|
|||
// ReSharper disable once CheckNamespace
|
|||
|
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies; |
|||
|
|||
[RemoteService(false)] |
|||
[DisableConventionalRegistration] |
|||
public partial class AbpApplicationConfigurationClientProxy |
|||
{ |
|||
} |
|||
|
|||
@ -1,16 +1,17 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Hosting; |
|||
using TickerQ.DependencyInjection; |
|||
using TickerQ.Utilities; |
|||
using TickerQ.Utilities.Enums; |
|||
using Volo.Abp.TickerQ; |
|||
|
|||
namespace Microsoft.AspNetCore.Builder; |
|||
namespace Microsoft.Extensions.Hosting; |
|||
|
|||
public static class AbpTickerQApplicationBuilderExtensions |
|||
{ |
|||
public static IApplicationBuilder UseAbpTickerQ(this IApplicationBuilder app, TickerQStartMode qStartMode = TickerQStartMode.Immediate) |
|||
public static IHost UseAbpTickerQ(this IHost app, TickerQStartMode qStartMode = TickerQStartMode.Immediate) |
|||
{ |
|||
var abpTickerQFunctionProvider = app.ApplicationServices.GetRequiredService<AbpTickerQFunctionProvider>(); |
|||
var abpTickerQFunctionProvider = app.Services.GetRequiredService<AbpTickerQFunctionProvider>(); |
|||
TickerFunctionProvider.RegisterFunctions(abpTickerQFunctionProvider.Functions); |
|||
TickerFunctionProvider.RegisterRequestType(abpTickerQFunctionProvider.RequestTypes); |
|||
|
|||
@ -0,0 +1,84 @@ |
|||
using System.Collections.Generic; |
|||
using System.Collections.ObjectModel; |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.ObjectMapping; |
|||
|
|||
public class ObjectMappingHelper_Tests |
|||
{ |
|||
[Fact] |
|||
public void IsCollectionGenericType_Should_Return_True_For_Standard_GenericCollection() |
|||
{ |
|||
var result = ObjectMappingHelper.IsCollectionGenericType<List<MappingTestSource>, List<MappingTestDestination>>( |
|||
out var sourceArg, out var destArg, out var defGenericType); |
|||
|
|||
result.ShouldBeTrue(); |
|||
sourceArg.ShouldBe(typeof(MappingTestSource)); |
|||
destArg.ShouldBe(typeof(MappingTestDestination)); |
|||
defGenericType.ShouldBe(typeof(List<>)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsCollectionGenericType_Should_Return_True_For_Array() |
|||
{ |
|||
var result = ObjectMappingHelper.IsCollectionGenericType<MappingTestSource[], MappingTestDestination[]>( |
|||
out var sourceArg, out var destArg, out _); |
|||
|
|||
result.ShouldBeTrue(); |
|||
sourceArg.ShouldBe(typeof(MappingTestSource)); |
|||
destArg.ShouldBe(typeof(MappingTestDestination)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsCollectionGenericType_Should_Normalize_IEnumerable_To_List() |
|||
{ |
|||
var result = ObjectMappingHelper.IsCollectionGenericType<IEnumerable<MappingTestSource>, IEnumerable<MappingTestDestination>>( |
|||
out _, out _, out var defGenericType); |
|||
|
|||
result.ShouldBeTrue(); |
|||
defGenericType.ShouldBe(typeof(List<>)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsCollectionGenericType_Should_Normalize_ICollection_To_Collection() |
|||
{ |
|||
var result = ObjectMappingHelper.IsCollectionGenericType<ICollection<MappingTestSource>, ICollection<MappingTestDestination>>( |
|||
out _, out _, out var defGenericType); |
|||
|
|||
result.ShouldBeTrue(); |
|||
defGenericType.ShouldBe(typeof(Collection<>)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsCollectionGenericType_Should_Return_False_For_NonCollection() |
|||
{ |
|||
var result = ObjectMappingHelper.IsCollectionGenericType<MappingTestSource, MappingTestDestination>( |
|||
out _, out _, out _); |
|||
|
|||
result.ShouldBeFalse(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsCollectionGenericType_Should_Return_False_For_NonGeneric_DerivedCollection() |
|||
{ |
|||
var result = ObjectMappingHelper.IsCollectionGenericType<List<MappingTestSource>, MappingTestDestinationList>( |
|||
out _, out _, out _); |
|||
|
|||
result.ShouldBeFalse(); |
|||
} |
|||
} |
|||
|
|||
public class MappingTestSource |
|||
{ |
|||
public string Value { get; set; } = ""; |
|||
} |
|||
|
|||
public class MappingTestDestination |
|||
{ |
|||
public string Value { get; set; } = ""; |
|||
} |
|||
|
|||
public class MappingTestDestinationList : List<MappingTestDestination> |
|||
{ |
|||
} |
|||
Loading…
Reference in new issue