From a123f89d1e395ab53858e93292e28141bc98faff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:09:25 +0000 Subject: [PATCH 1/3] Initial plan From 95fc87c60265633892e8637a073eb9b8673db9e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:13:57 +0000 Subject: [PATCH 2/3] Update localization docs to clarify ResourcesPath behavior with shared resource classes Co-authored-by: BillWagner <493969+BillWagner@users.noreply.github.com> --- docs/core/extensions/localization.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/core/extensions/localization.md b/docs/core/extensions/localization.md index c3d179b9f798d..1795790875521 100644 --- a/docs/core/extensions/localization.md +++ b/docs/core/extensions/localization.md @@ -1,7 +1,8 @@ --- title: Localization description: Learn the concepts of localization while learning how to use the IStringLocalizer and IStringLocalizerFactory implementations in your .NET workloads. -ms.date: 10/20/2025 +ms.date: 03/12/2026 +ai-usage: ai-assisted helpviewer_keywords: - "culture, localization" - "application development [.NET], localization" @@ -110,6 +111,28 @@ Resource files can live anywhere in a project, but there are common practices in This would cause the localization services to look in the *Resources* directory for resource files. +### ResourcesPath and shared resource classes + +When `ResourcesPath` is configured, `IStringLocalizerFactory.Create(Type)` resolves the resource file path by computing the type's relative name (relative to the root namespace) and prepending the `ResourcesPath`. This means the placement of your shared resource class relative to the project root namespace matters. + +For example, consider a shared resource class used to consolidate strings across multiple components. If `ResourcesPath = "Resources"` is set and your shared resource class `SharedResource` lives *inside* the `Resources` folder with namespace `MyApp.Resources.SharedResource`, the factory resolves the base name as `Resources.SharedResource` (relative to the root namespace `MyApp`) and then prepends `ResourcesPath`, resulting in a path equivalent to `Resources/Resources/SharedResource.resx`—which doesn't match the actual file location. + +You have two options to avoid this: + +- **Keep the shared resource class in the root namespace.** Place `SharedResource.cs` at the project root (namespace `MyApp`), not inside the `Resources` folder. The factory then resolves the base name as `SharedResource`, and with `ResourcesPath = "Resources"`, it correctly looks for `Resources/SharedResource.resx`. + +- **Use `factory.Create(string baseName, string location)`.** Provide the base name and assembly name explicitly, which gives direct control over resource file resolution: + + ```csharp + var assemblyName = new AssemblyName(typeof(SharedResource).GetTypeInfo().Assembly.FullName); + IStringLocalizer localizer = factory.Create("SharedResource", assemblyName.Name); + ``` + + This approach works regardless of where `SharedResource.cs` is located in the project. + +> [!NOTE] +> The same consideration applies when integrating with frameworks that use `IStringLocalizerFactory` internally. For example, ASP.NET Core's `AddDataAnnotationsLocalization` uses this factory with a shared resource class. If `ResourcesPath` is configured, use the `factory.Create(string, string)` overload. Alternatively, ensure the shared resource class is in the root namespace. + ## Use `IStringLocalizer` and `IStringLocalizerFactory` After you've [registered](#register-localization-services) (and optionally [configured](#configure-localization-options)) the localization services, you can use the following types with DI: From d624e797d2e339b8f71464907d5a9a0f04acd54b Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 12 Mar 2026 16:18:20 -0400 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/core/extensions/localization.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/core/extensions/localization.md b/docs/core/extensions/localization.md index 1795790875521..fbbd1cf533afc 100644 --- a/docs/core/extensions/localization.md +++ b/docs/core/extensions/localization.md @@ -115,7 +115,7 @@ This would cause the localization services to look in the *Resources* directory When `ResourcesPath` is configured, `IStringLocalizerFactory.Create(Type)` resolves the resource file path by computing the type's relative name (relative to the root namespace) and prepending the `ResourcesPath`. This means the placement of your shared resource class relative to the project root namespace matters. -For example, consider a shared resource class used to consolidate strings across multiple components. If `ResourcesPath = "Resources"` is set and your shared resource class `SharedResource` lives *inside* the `Resources` folder with namespace `MyApp.Resources.SharedResource`, the factory resolves the base name as `Resources.SharedResource` (relative to the root namespace `MyApp`) and then prepends `ResourcesPath`, resulting in a path equivalent to `Resources/Resources/SharedResource.resx`—which doesn't match the actual file location. +For example, consider a shared resource class used to consolidate strings across multiple components. If `ResourcesPath = "Resources"` is set and your shared resource class `SharedResource` lives *inside* the `Resources` folder in the namespace `MyApp.Resources` (fully qualified type name `MyApp.Resources.SharedResource`), the factory resolves the base name as `Resources.SharedResource` (relative to the root namespace `MyApp`) and then prepends `ResourcesPath`, resulting in a path equivalent to `Resources/Resources/SharedResource.resx`—which doesn't match the actual file location. You have two options to avoid this: @@ -124,8 +124,8 @@ You have two options to avoid this: - **Use `factory.Create(string baseName, string location)`.** Provide the base name and assembly name explicitly, which gives direct control over resource file resolution: ```csharp - var assemblyName = new AssemblyName(typeof(SharedResource).GetTypeInfo().Assembly.FullName); - IStringLocalizer localizer = factory.Create("SharedResource", assemblyName.Name); + var assemblyName = typeof(SharedResource).Assembly.GetName().Name; + IStringLocalizer localizer = factory.Create("SharedResource", assemblyName); ``` This approach works regardless of where `SharedResource.cs` is located in the project.