Msbuild: Satellite assemblies are not produced for registered custom cultures when using .NET Core msbuild

Created on 8 Dec 2016  路  20Comments  路  Source: dotnet/msbuild

Moving from https://github.com/dotnet/sdk/issues/387 (originally https://github.com/dotnet/cli/issues/4050) on behalf of @ryanbrandenburg.


Steps to reproduce

  1. Register a custom culture using something like sysglobl.dll and:
var car1 = new CultureAndRegionInfoBuilder("ru-US", CultureAndRegionModifiers.None);
car1.LoadDataFromCultureInfo(CultureInfo.CreateSpecificCulture("ru-RU"));
car1.LoadDataFromRegionInfo(new RegionInfo("en-US"));

car1.CultureEnglishName = "Russion (United States)";
car1.CultureNativeName = "褉褍褋褋泻懈泄 (小楔袗)";
car1.CurrencyNativeName = "袛芯谢谢邪褉 (小楔袗)";
car1.RegionNativeName = "小楔袗";

// Register the culture.
try
{
    car1.Register();
}
catch (InvalidOperationException)
{
    // Swallow the exception: the culture already is registered.
}
  1. Create a new ASP.NET Core Web Application (.NET Core)
  2. Create two resources, one named Resource.en-US.resx and one named Resource.ru-US.resx
  3. Build the project.
  4. Open bin/Debug/net451
  5. Observe that a en-US folder is present and has a dll and that ru-US is missing

    Expected behavior

Any culture which will work with new CultureInfo("culture-NAME") (which custom cultures do once registered as above) should create a folder and dll which can be used in localization.

Actual behavior

Satellite assemblies are only created for original cultures, not for custom cultures.

Environment data

dotnet --info output:
.NET Command Line Tools (1.0.0-preview2-003121)

Product Information:
Version: 1.0.0-preview2-003121
Commit SHA-1 hash: 1e9d529bc5

Runtime Environment:
OS Name: Windows
OS Version: 10.0.10586
OS Platform: Windows
RID: win10-x64

Most helpful comment

Hi

This issue is still happening.

Satellite assemblies for custom cultures are still not generated.

Using the provided workaround generates the satellite assemblies for the referenced projects, but they are not copied to the main executable project.

To make this more misleading, if you have a webapi project with custom culture resources in the main project, you can access those resources without any problem. In this case they're embedded resources and not satellite assemblies. But if you have a referenced assembly, it generates satellite assemblies only for known cultures, but not custom cultures.

(Moving dotnet/sdk#387 (comment))

This will work fine if you build using desktop MSBuild / Visual Studio, but .NET Core MSBuild has this.

This can be worked around with a custom target that lets any culture through:

  <Target Name="AssignCustomCultures" AfterTargets="SplitResourcesByCulture">
    <ItemGroup>
      <EmbeddedResource Condition="$([System.IO.Path]::HasExtension(%(Filename)))">
        <Culture>$([System.IO.Path]::GetExtension(%(Filename)).TrimStart('.'))</Culture>
        <WithCulture>true</WithCulture>
      </EmbeddedResource>
    </ItemGroup>
  </Target>

All 20 comments

(Moving https://github.com/dotnet/sdk/issues/387#issuecomment-265608133)

This will work fine if you build using desktop MSBuild / Visual Studio, but .NET Core MSBuild has this.

This can be worked around with a custom target that lets any culture through:

  <Target Name="AssignCustomCultures" AfterTargets="SplitResourcesByCulture">
    <ItemGroup>
      <EmbeddedResource Condition="$([System.IO.Path]::HasExtension(%(Filename)))">
        <Culture>$([System.IO.Path]::GetExtension(%(Filename)).TrimStart('.'))</Culture>
        <WithCulture>true</WithCulture>
      </EmbeddedResource>
    </ItemGroup>
  </Target>

This approach has the advantage that it is not dependent on what is registered on the build machine.

@rainersigwald Thoughts? Should msbuild be more lenient about culture names to allow for custom cultures? I'm not too keen on relying on the build machine having registered a custom culture, but that does at least work with desktop msbuild. However, I don't know if .NET Core BCL provides enough to replicate that.

I remember talking about this when @cdmihai implemented the current mechanism but I don't know enough about the problem space to have a good opinion off the bat.

Speaking from the hip, it seems like we should either always allow arbitrary cultures or stick to some sort of list. I agree that registering the culture by changing OS state on the build machine is kind of an icky way to enable this.

On Full Framework MSBuild populates a list of all valid culture names via CultureInfo.GetCultures. That method does not exist on .Net Core, so we just use the hardcoded list FF gave us.

We hit this in MSBuild's own build because we have a resource named Strings.Shared.Resx. This caused a runtime crash with missing resources on .Net Core because the resource was assigned the "Shared" culture. It works on FF because FF successfully rejects "Shared" as a culture.

Here's one solution: accept everything on .net core, and introduce a ThisIsNotACulture metadata for EmbeddedResource which tells the AssignCulture task to interpret it as neutral.

P.S.: We went with the hardcoded list to mimic the FF behaviour as close as possible. If universal strings failed for our build, then it could fail for others too. Maybe we'll get the API back with ns2.0

How about making the list a property or items that the user can amend.

Btw, the first thing I tried was to explicitly give WithCulture=true, Culture=ru-US to a static item but AssignCulture wacks that, which is why my workaround evolved in to a target.

Another approach would be to respect explicit WithCulture metadata as implying that no check is required.

Yup, sounds like we have to implement something that respects current metadata to ignore / enforce file name based culture inference. Or as you said, look into extending the hardcoded list of cultures.

In terms of planning, when should this change go in? How often is it that users create their own custom cultures?

I don't know about the usage but I suspect it's low. Given that there is a workaround of setting metadata in a custom rather, I'd think we could get away with vNext.

Is there a bug here? GenerateSatelliteAssemblies only runs where MSBuildRuntimeType != Core but ComputeIntermediateSatelliteAssemblies which is dependent on it runs in all cases.

I've tried following the above bit am still hitting the same issue.

@nguerrera, ping in case you're not still following this.

It's an evil, hacky workaround, but dropping the line
<Target Name="ComputeIntermediateSatelliteAssemblies"></Target>
into the problem project file seems to get past this for now.

@adsurg It's not clear what you are working around. Can you open a new issue with repro steps and the precise failure that you're seeing. It is not clear that this is the same as the root issue, which is about custom cultures. There are targets that are core runtime only for satellite only because core msbuild doesn't support building satellites with its own targets, but anything that does not run on core has an alternate on full framework msbuild that does run.

@nguerrera
Hi Nick,
I tried with vs build and it doesn't generate the custom culture dll.
I tried the workaround you proposed. Adding that target on the xproj and it gives me
"Invalid static method invocation syntax:
"[System.IO.Path]::HasExtension()". Method 'System.IO.Path.HasExtension' not found. "

I think I am missing something. Do you have any idea?

This issue is not limited to the custom cultures but also to the cultures that can be created by the framework but not enumerated with CultureInfo.GetCultures. for example, on Windows 10, you can create any culture even if the OS doesn't have data for (something like yy-YY). also on Linux if using one of the aliased cultures.

Hi

This issue is still happening.

Satellite assemblies for custom cultures are still not generated.

Using the provided workaround generates the satellite assemblies for the referenced projects, but they are not copied to the main executable project.

To make this more misleading, if you have a webapi project with custom culture resources in the main project, you can access those resources without any problem. In this case they're embedded resources and not satellite assemblies. But if you have a referenced assembly, it generates satellite assemblies only for known cultures, but not custom cultures.

(Moving dotnet/sdk#387 (comment))

This will work fine if you build using desktop MSBuild / Visual Studio, but .NET Core MSBuild has this.

This can be worked around with a custom target that lets any culture through:

  <Target Name="AssignCustomCultures" AfterTargets="SplitResourcesByCulture">
    <ItemGroup>
      <EmbeddedResource Condition="$([System.IO.Path]::HasExtension(%(Filename)))">
        <Culture>$([System.IO.Path]::GetExtension(%(Filename)).TrimStart('.'))</Culture>
        <WithCulture>true</WithCulture>
      </EmbeddedResource>
    </ItemGroup>
  </Target>

Can you use the Copy task to move them right after they're generated?

to fix this, msbuild has to change the way depending on culture enumerated list and instead try to validate the culture by trying to create it. CultureInfo.GetCultureInfo can be used as it caches the previously created culture anyway.

Given that the issue has been present for 3 1/2 years, how likely is a fix at this point?

Can you use the Copy task to move them right after they're generated?

Not really well. I can make it to work partially. But as far as I have seen, I need to add the task from nguerrera above, to every project that has those resources, then add the copy command to all projects in cascade, and a command for each dependency using project paths.

I made the test for a couple of projects in a solution and for some reason it worked for one of them but not for the other with identical configurations. No idea why, but the process seems so time and work consuming and prone to failure that doesn't seem a good idea to go ahead putting it to work.

Maybe I missed something, and is easier than it looks...

Was this page helpful?
0 / 5 - 0 ratings