The answer to my original question appears to be "No". With regard to localization, shared projects don't really work.
However, localization can work, but there are two problems.
1. The custom tool PublicResXFileCodeGenerator doesn't run.
In my own tool (which is a visual studio extension), I will try to generate the code behind (Resources.designer.cs) myself. The code required to generate it is quite simple.
This is my basic function ...
public void GenerateCodeCS ( string resxFullPath, string namespaceName, ITypeResolutionService typeResolver )
{
string BaseName = Path.GetFileNameWithoutExtension ( resxFullPath ) ;
string BaseDir = Path.GetDirectoryName ( resxFullPath ) ;
string FileName = BaseName + ".designer.cs" ;
string FullPath = Path.Combine ( BaseDir, FileName ) ;
using ( var sw = new StreamWriter ( FullPath, false, Encoding.UTF8 ) )
{
sw.WriteLine ( $"namespace {namespaceName}.Properties" ) ;
sw.WriteLine ( "{" );
sw.WriteLine ( " using System;" );
sw.WriteLine ( " using System.Resources;" );
sw.WriteLine ( " using System.Globalization;" );
sw.WriteLine ( " public class Resources" );
sw.WriteLine ( " {" );
sw.WriteLine ( " private static ResourceManager resourceMan;" );
sw.WriteLine ( " private static CultureInfo resourceCulture;" );
sw.WriteLine ();
sw.WriteLine ( " public static global::System.Resources.ResourceManager ResourceManager" );
sw.WriteLine ( " {" );
sw.WriteLine ( " get" );
sw.WriteLine ( " {" );
sw.WriteLine ( " if ( resourceMan == null )" );
sw.WriteLine ( " {" );
sw.WriteLine ($" resourceMan = new ResourceManager(\"{namespaceName}.Properties.Resources\", typeof(Resources).Assembly);" );
sw.WriteLine ( " }" );
sw.WriteLine ( " return resourceMan ;" );
sw.WriteLine ( " }" );
sw.WriteLine ( " }" );
sw.WriteLine ();
sw.WriteLine ( " public static CultureInfo Culture { get => resourceCulture; set => resourceCulture = value; }" );
sw.WriteLine ();
using ( var resxReader = new ResXResourceReader ( resxFullPath, typeResolver ) )
{
resxReader.BasePath = BaseDir ;
resxReader.UseResXDataNodes = true ;
foreach ( DictionaryEntry res in resxReader as IResourceReader )
{
var key = res.Key.ToString() ;
var resxNode = res.Value as ResXDataNode ;
var value = resxNode.GetValue ( typeResolver ) ;
if ( value is string )
{
sw.WriteLine ( $" public static string {key} => ResourceManager.GetString(\"{key}\", resourceCulture);" ) ;
}
}
}
sw.WriteLine ( " }" );
sw.WriteLine ( "}" );
}
}
This generates a file something like this
namespace SharedProject.Properties
{
using System;
using System.Resources;
using System.Globalization;
public class Resources
{
private static ResourceManager resourceMan;
private static CultureInfo resourceCulture;
public static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ( resourceMan == null )
{
resourceMan = new ResourceManager("SharedProject.Properties.Resources", typeof(Resources).Assembly);
}
return resourceMan ;
}
}
public static CultureInfo Culture { get => resourceCulture; set => resourceCulture = value; }
public static string Bye_Bye => ResourceManager.GetString("Bye_Bye", resourceCulture);
public static string Ciao_Ciao => ResourceManager.GetString("Ciao_Ciao", resourceCulture);
public static string Hello_World => ResourceManager.GetString("Hello_World", resourceCulture);
}
}
It should be possible to include that in a console app and to run it in a pre-build step. The typeResolver parameter is probably not necessary.
2. The namespace for the resources is wrong.
When the resources are compiled into the target application, they are given the namespace of the application, and not the namespace of the shared project.
Since the code in the shared project cannot possibly know the namespace of the target application, this makes it impossible to access the resources.
Fortunately, there is a workaround for this problem. What you have to do, is to edit the project file and add the <LogicalName>
property to the resource file.
Actually, in a shared project, it is not the project file (.csproj), but the file project items file (.projitems).
In my example project, the resource file is referenced like this
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LogicalName>SharedProject.Properties.Resources.resources</LogicalName>
</EmbeddedResource>
In this case, SharedProject is the namespace of the shared project, which is referenced by the code behind (see above).
Of course the <Generator>
property is pointless, because the custom tool does not run.
I am not aware of any way to add the property via the GUI or even from a visual studio extension.
Using ILSpy, I can see that the resources are now embedded into my application with the namespace of the shared project

and loading the resources works.