@@ -14,12 +14,17 @@ namespace Microsoft.Extensions.Localization
1414 /// <summary>
1515 /// An <see cref="IStringLocalizerFactory"/> that creates instances of <see cref="ResourceManagerStringLocalizer"/>.
1616 /// </summary>
17+ /// <remarks>
18+ /// <see cref="ResourceManagerStringLocalizerFactory"/> offers multiple ways to set the relative path of
19+ /// resources to be used. They are, in order of precedence:
20+ /// <see cref="ResourceLocationAttribute"/> -> <see cref="LocalizationOptions.ResourcesPath"/> -> the project root.
21+ /// </remarks>
1722 public class ResourceManagerStringLocalizerFactory : IStringLocalizerFactory
1823 {
1924 private readonly IResourceNamesCache _resourceNamesCache = new ResourceNamesCache ( ) ;
2025 private readonly ConcurrentDictionary < string , ResourceManagerStringLocalizer > _localizerCache =
2126 new ConcurrentDictionary < string , ResourceManagerStringLocalizer > ( ) ;
22- private readonly IHostingEnvironment _hostingEnvironment ;
27+ private readonly string _applicationName ;
2328 private readonly string _resourcesRelativePath ;
2429
2530 /// <summary>
@@ -41,7 +46,7 @@ public ResourceManagerStringLocalizerFactory(
4146 throw new ArgumentNullException ( nameof ( localizationOptions ) ) ;
4247 }
4348
44- _hostingEnvironment = hostingEnvironment ;
49+ _applicationName = hostingEnvironment . ApplicationName ;
4550 _resourcesRelativePath = localizationOptions . Value . ResourcesPath ?? string . Empty ;
4651 if ( ! string . IsNullOrEmpty ( _resourcesRelativePath ) )
4752 {
@@ -62,7 +67,7 @@ protected virtual string GetResourcePrefix(TypeInfo typeInfo)
6267 throw new ArgumentNullException ( nameof ( typeInfo ) ) ;
6368 }
6469
65- return GetResourcePrefix ( typeInfo , _hostingEnvironment . ApplicationName , _resourcesRelativePath ) ;
70+ return GetResourcePrefix ( typeInfo , new AssemblyName ( typeInfo . Assembly . FullName ) . Name , _resourcesRelativePath ) ;
6671 }
6772
6873 /// <summary>
@@ -106,9 +111,16 @@ protected virtual string GetResourcePrefix(string baseResourceName, string baseN
106111 throw new ArgumentNullException ( nameof ( baseResourceName ) ) ;
107112 }
108113
109- var locationPath = baseNamespace == _hostingEnvironment . ApplicationName ?
110- baseNamespace + "." + _resourcesRelativePath :
111- baseNamespace + "." ;
114+ if ( string . IsNullOrEmpty ( baseNamespace ) )
115+ {
116+ throw new ArgumentNullException ( nameof ( baseNamespace ) ) ;
117+ }
118+
119+ var assemblyName = new AssemblyName ( baseNamespace ) ;
120+ var assembly = Assembly . Load ( assemblyName ) ;
121+ var resourceLocation = GetResourcePath ( assembly ) ;
122+ var locationPath = baseNamespace + "." + resourceLocation ;
123+
112124 baseResourceName = locationPath + TrimPrefix ( baseResourceName , baseNamespace + "." ) ;
113125
114126 return baseResourceName ;
@@ -129,17 +141,12 @@ public IStringLocalizer Create(Type resourceSource)
129141
130142 var typeInfo = resourceSource . GetTypeInfo ( ) ;
131143 var assembly = typeInfo . Assembly ;
144+ var assemblyName = new AssemblyName ( assembly . FullName ) ;
145+ var resourcePath = GetResourcePath ( assembly ) ;
132146
133- // Re-root the base name if a resources path is set
134- var baseName = GetResourcePrefix ( typeInfo ) ;
135-
136- return _localizerCache . GetOrAdd ( baseName , _ =>
137- new ResourceManagerStringLocalizer (
138- new ResourceManager ( baseName , assembly ) ,
139- assembly ,
140- baseName ,
141- _resourceNamesCache )
142- ) ;
147+ var baseName = GetResourcePrefix ( typeInfo , assemblyName . Name , resourcePath ) ;
148+
149+ return _localizerCache . GetOrAdd ( baseName , _ => CreateResourceManagerStringLocalizer ( assembly , baseName ) ) ;
143150 }
144151
145152 /// <summary>
@@ -155,21 +162,71 @@ public IStringLocalizer Create(string baseName, string location)
155162 throw new ArgumentNullException ( nameof ( baseName ) ) ;
156163 }
157164
158- location = location ?? _hostingEnvironment . ApplicationName ;
159-
160- baseName = GetResourcePrefix ( baseName , location ) ;
165+ location = location ?? _applicationName ;
161166
162167 return _localizerCache . GetOrAdd ( $ "B={ baseName } ,L={ location } ", _ =>
163168 {
164- var assembly = Assembly . Load ( new AssemblyName ( location ) ) ;
165- return new ResourceManagerStringLocalizer (
166- new ResourceManager ( baseName , assembly ) ,
167- assembly ,
168- baseName ,
169- _resourceNamesCache ) ;
169+ var assemblyName = new AssemblyName ( location ) ;
170+ var assembly = Assembly . Load ( assemblyName ) ;
171+ baseName = GetResourcePrefix ( baseName , location ) ;
172+
173+ return CreateResourceManagerStringLocalizer ( assembly , baseName ) ;
170174 } ) ;
171175 }
172176
177+ /// <summary>Creates a <see cref="ResourceManagerStringLocalizer"/> for the given input.</summary>
178+ /// <param name="assembly">The assembly to create a <see cref="ResourceManagerStringLocalizer"/> for.</param>
179+ /// <param name="baseName">The base name of the resource to search for.</param>
180+ /// <returns>A <see cref="ResourceManagerStringLocalizer"/> for the given <paramref name="assembly"/> and <paramref name="baseName"/>.</returns>
181+ /// <remarks>This method is virtual for testing purposes only.</remarks>
182+ protected virtual ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer (
183+ Assembly assembly ,
184+ string baseName )
185+ {
186+ return new ResourceManagerStringLocalizer (
187+ new ResourceManager ( baseName , assembly ) ,
188+ assembly ,
189+ baseName ,
190+ _resourceNamesCache ) ;
191+ }
192+
193+ /// <summary>
194+ /// Gets the resource prefix used to look up the resource.
195+ /// </summary>
196+ /// <param name="location">The general location of the resource.</param>
197+ /// <param name="baseName">The base name of the resource.</param>
198+ /// <param name="resourceLocation">The location of the resource within <paramref name="location"/>.</param>
199+ /// <returns>The resource prefix used to look up the resource.</returns>
200+ protected virtual string GetResourcePrefix ( string location , string baseName , string resourceLocation )
201+ {
202+ // Re-root the base name if a resources path is set
203+ return location + "." + resourceLocation + TrimPrefix ( baseName , location + "." ) ;
204+ }
205+
206+ /// <summary>Gets a <see cref="ResourceLocationAttribute"/> from the provided <see cref="Assembly"/>.</summary>
207+ /// <param name="assembly">The assembly to get a <see cref="ResourceLocationAttribute"/> from.</param>
208+ /// <returns>The <see cref="ResourceLocationAttribute"/> associated with the given <see cref="Assembly"/>.</returns>
209+ /// <remarks>This method is protected and virtual for testing purposes only.</remarks>
210+ protected virtual ResourceLocationAttribute GetResourceLocationAttribute ( Assembly assembly )
211+ {
212+ return assembly . GetCustomAttribute < ResourceLocationAttribute > ( ) ;
213+ }
214+
215+ private string GetResourcePath ( Assembly assembly )
216+ {
217+ var resourceLocationAttribute = GetResourceLocationAttribute ( assembly ) ;
218+
219+ // If we don't have an attribute assume all assemblies use the same resource location.
220+ var resourceLocation = resourceLocationAttribute == null
221+ ? _resourcesRelativePath
222+ : resourceLocationAttribute . ResourceLocation + "." ;
223+ resourceLocation = resourceLocation
224+ . Replace ( Path . DirectorySeparatorChar , '.' )
225+ . Replace ( Path . AltDirectorySeparatorChar , '.' ) ;
226+
227+ return resourceLocation ;
228+ }
229+
173230 private static string TrimPrefix ( string name , string prefix )
174231 {
175232 if ( name . StartsWith ( prefix , StringComparison . Ordinal ) )
0 commit comments