0

在 Visual Studio 中,您可以创建共享项目

Microsoft 是否设想过任何标准方法在共享项目中进行本地化?

我很确定我可以找到一种处理本地化的方法,但是在我发明自己的方法之前,很高兴知道是否有“官方”方法。

编辑

如果您对库进行本地化,则翻译应包含在库中。

如果您使用 .resx(或 .resw)文件中的资源字符串本地化文本,则资源文件必须包含在库中,并且必须有代码从 rignt 资源文件加载资源。

有两件事是不可能的。

  1. 仅在使用该库的组件中进行本地化。
    如果您用资源名称替换字符串文字,您正在更改源代码,这与使用库的整个想法背道而驰。

  2. 在库中使用资源名称,而不提供 .resx 文件。
    那个代码是行不通的。

Microsoft 论坛中有一个类似的问题。根据该页面上的讨论,在我看来微软没有为共享项目的本地化提供足够的支持。

我发现了一个有趣的例子,它是GitHub 上的项目 CodeMaid 项目

这是一个 Visual Studio 扩展,而不是 Xamarin 项目(共享项目不限于 Xamarin)。该项目包含一个资源文件 (Resource.resx) 和一个用于访问资源的代码文件 (Resources.resx.cs)。基于此,我假设可以在共享项目中包含 .resx 文件。

但是,似乎无法运行自定义工具 (PublicResXFileCodeGenerator) 来生成代码文件,并且在您编辑 .resx 文件时它不会自动运行。

我的猜测是,CodeMaid 的开发人员没有对资源文件进行任何更改,因为将其移动到共享项目中。

4

1 回答 1

0

我原来的问题的答案似乎是“否”。关于本地化,共享项目并没有真正起作用。

然而,本地化可以工作,但有两个问题。

1. 自定义工具 PublicResXFileCodeGenerator 没有运行。

在我自己的工具(这是一个 Visual Studio 扩展)中,我将尝试自己生成 (Resources.designer.cs) 背后的代码。生成它所需的代码非常简单。

这是我的基本功能...

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 ( "}" );
  }
}

这会生成一个类似这样的文件

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);
  }
}

应该可以将其包含在控制台应用程序中并在预构建步骤中运行它。typeResolver 参数可能不是必需的。

2. 资源的命名空间错误。

当资源被编译到目标应用程序中时,它们被赋予应用程序的命名空间,而不是共享项目的命名空间。

由于共享项目中的代码不可能知道目标应用程序的命名空间,因此无法访问资源。

幸运的是,有解决此问题的方法。您要做的是编辑项目文件并将<LogicalName>属性添加到资源文件中。

实际上,在共享项目中,它不是项目文件(.csproj),而是文件项目项目文件(.projitems)。

在我的示例项目中,资源文件是这样引用的

<EmbeddedResource Include="$(MSBuildThisFileDirectory)Properties\Resources.resx">
  <Generator>PublicResXFileCodeGenerator</Generator>
  <LogicalName>SharedProject.Properties.Resources.resources</LogicalName>
</EmbeddedResource>

在这种情况下,SharedProject是共享项目的命名空间,由后面的代码引用(见上文)。

当然该<Generator>属性是没有意义的,因为自定义工具没有运行。

我不知道通过 GUI 甚至从 Visual Studio 扩展添加属性的任何方法。

使用 ILSpy,我可以看到资源现在使用共享项目的命名空间嵌入到我的应用程序中

在此处输入图像描述

并加载资源工作。

于 2022-02-11T19:23:23.190 回答