将已翻译的resx文件放在Visual Studio中的其他文件夹中?

时间:2015-10-08 06:16:53

标签: .net visual-studio globalization resx

我有一个包含14个翻译和12个文件的解决方案,而且“Resources”文件夹有点失控。有没有办法将翻译后的文件放到另一个文件夹中,这样我就可以更轻松地访问英语主文件了?

我注意到您可以通过设置自定义工具命名空间来更改生成设计器文件的命名空间,但我还没想出如何从其他文件夹中获取翻译。我玩弄了使用链接制作“快捷方式”文件夹和“真实”文件夹的想法,但是你无法链接到项目结构中的文件而且你不能在项目结构之外链接到同一个文件两次。我考虑过根据语言更改ResourceManager使用的命名空间,但这会破坏回退行为。

有没有办法管理这个,所以它不是解决方案资源管理器中的巨大文件列表?

3 个答案:

答案 0 :(得分:2)

我们假设您要将所有已翻译的.resx文件放在Translations文件夹中,如下所示(请注意,我已将默认 en < / em>根文件夹中的版本):

enter image description here

然后您需要做的就是编辑MSBuild项目文件(从Visual Studio,右键单击项目节点,然后“卸载项目”,然后编辑xxx.csproj&#39; ),并重新配置此文件夹中的每个Resx文件,如下所示:

之前:

  <ItemGroup>
    ...
    <EmbeddedResource Include="Resource1.en.resx" />
    <EmbeddedResource Include="Translations\Resource1.fr.resx" />
    <EmbeddedResource Include="Resource1.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>Resource1.Designer.cs</LastGenOutput>
    </EmbeddedResource>
    ...
  </ItemGroup>

后:

  <ItemGroup>
    ...
    <EmbeddedResource Include="Resource1.en.resx" />
    <EmbeddedResource Include="Translations\Resource1.fr.resx">
      <ManifestResourceName>$(TargetName).%(Filename)</ManifestResourceName> <!-- add this for all .resx files in a custom folder -->
    </EmbeddedResource>
    <EmbeddedResource Include="Resource1.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>Resource1.Designer.cs</LastGenOutput>
    </EmbeddedResource>
    ...
  </ItemGroup>

它的作用是指示底层MSBuild任务使用您特定的清单名称,而不是自动生成一个。自动生成的文件总是使用RESX所在的文件夹布局,而不是我们想要的。

编译后,最终的卫星ConsoleApplication1.resources.dll文件将像往常一样放在fr文件夹中,但其内容与将resx文件放在根文件夹中的内容相同。

答案 1 :(得分:1)

实现此目标的一种方法是编写自定义资源提供程序。 Rick Strahl写了一个来从数据库而不是RESX文件中提供资源。他继承的类是mscorlib中的System.Resources.ResourceManager。这是他的实施。

https://github.com/RickStrahl/Westwind.Globalization/blob/master/Westwind.Globalization/DbResourceManager/DbResourceManager.cs

这是该文章的链接,他提供了代码。

http://weblog.west-wind.com/posts/2014/Mar/31/Updated-ASPNET-Database-Resource-Provider

我意识到你不想存储来自数据库的资源,但原则应该适用

答案 2 :(得分:1)

我明白了。我需要在ResourceManager中做一些技巧来加载附属程序集并引入.resources嵌入式资源。您可以传入资源类型,可以找到默认/主资源的命名空间,以及可以找到翻译的命名空间。

public class OrganizingResourceManager : ResourceManager
{
    /// <summary>
    /// Key is culture, value is the assembly that contains the resources for the culture.
    /// </summary>
    private static readonly Dictionary<string, Assembly> SatelliteAssemblies = new Dictionary<string, Assembly>();

    /// <summary>
    /// Key is culture, value is the set of resource names in the assembly for the culture.
    /// </summary>
    private static readonly Dictionary<string, HashSet<string>> ResourceNames = new Dictionary<string, HashSet<string>>();

    /// <summary>
    /// Key is culture, value is the <see cref="ResourceSet"/> for the culture.
    /// </summary>
    private readonly Dictionary<string, ResourceSet> CachedResourceSets = new Dictionary<string, ResourceSet>();

    private Type resourceType;
    private string defaultResourcesNamespace;
    private string translatedResourcesNamespace;

    public OrganizingResourceManager(Type resourceType, string defaultResourcesNamespace, string translatedResourcesNamespace)
    {
        this.translatedResourcesNamespace = translatedResourcesNamespace;
        this.defaultResourcesNamespace = defaultResourcesNamespace;
        this.resourceType = resourceType;
    }

    /// <summary>
    /// Provides the implementation for finding a resource set.
    /// </summary>
    /// <param name="culture">The culture to look for.</param>
    /// <param name="createIfNotExists">true to load the resource set, if it has not been loaded yet; otherwise, false.</param>
    /// <param name="tryParents">true to check parent CultureInfo objects if the resource set cannot be loaded; otherwise, false.</param>
    /// <returns>The specified resource set.</returns>
    protected override ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
    {
        ResourceSet result;
        if (this.CachedResourceSets.TryGetValue(culture.Name, out result))
        {
            return result;
        }

        if (tryParents)
        {
            result = this.GetResourceSetRecursive(culture);
        }
        else
        {
            result = this.LoadResourceSet(culture.Name);
        }

        this.CachedResourceSets.Add(culture.Name, result);

        return result;
    }

    /// <summary>
    /// Finds a resource set for the given culture, recursively.
    /// </summary>
    /// <param name="culture">The culture to look for.</param>
    /// <returns>The ResourceSet for the culture.</returns>
    private ResourceSet GetResourceSetRecursive(CultureInfo culture)
    {
        ResourceSet resourceSet = this.LoadResourceSet(culture.Name);
        if (resourceSet == null && culture.Name != string.Empty)
        {
            return this.GetResourceSetRecursive(culture.Parent);
        }
        else
        {
            return resourceSet;
        }
    }

    /// <summary>
    /// Loads in a ResourceSet for a culture.
    /// </summary>
    /// <param name="cultureName">The culture's name.</param>
    /// <returns>The ResourceSet or null if no resources with that culture could be found.</returns>
    private ResourceSet LoadResourceSet(string cultureName)
    {
        Assembly assembly = this.GetAssembly(cultureName);
        if (assembly == null)
        {
            // No satellite assembly found.
            return null;
        }
        else
        {
            string resourcePath = this.GetResourceName(cultureName);

            if (ResourceNames[cultureName].Contains(resourcePath))
            {
                using (var stream = assembly.GetManifestResourceStream(resourcePath))
                {
                    return new ResourceSet(stream);
                }
            }
        }

        return null;
    }

    /// <summary>
    /// Gets the resource name for the given culture.
    /// </summary>
    /// <param name="cultureName">The culture's name.</param>
    /// <returns>The resource name for the culture.</returns>
    private string GetResourceName(string cultureName)
    {
        var builder = new StringBuilder();

        if (cultureName == string.Empty)
        {
            builder.Append(this.defaultResourcesNamespace);
        }
        else
        {
            builder.Append(this.translatedResourcesNamespace);
        }

        builder.Append(".");
        builder.Append(this.resourceType.Name);

        if (cultureName != string.Empty)
        {
            builder.Append(".");
            builder.Append(cultureName);
        }

        builder.Append(".resources");

        return builder.ToString();
    }

    /// <summary>
    /// Gets the resource assembly for a culture.
    /// </summary>
    /// <param name="cultureName">The culture's name.</param>
    /// <returns>The assembly for the culture or null if no assembly could be found. Returns a satellite assembly if a
    /// specific culture or the main assembly if an invariant culture.</returns>
    private Assembly GetAssembly(string cultureName)
    {
        Assembly assembly;
        if (SatelliteAssemblies.TryGetValue(cultureName, out assembly))
        {
            return assembly;
        }

        if (cultureName == string.Empty)
        {
            assembly = this.resourceType.Assembly;
        }
        else
        {
            string assemblyLocation = Assembly.GetExecutingAssembly().Location;
            string assemblyName = Path.GetFileNameWithoutExtension(assemblyLocation) + ".resources.dll";

            string assemblyPath = Path.Combine(
                Path.GetDirectoryName(assemblyLocation),
                cultureName,
                assemblyName);
            if (!File.Exists(assemblyPath))
            {
                return null;
            }

            assembly = Assembly.LoadFile(assemblyPath);
        }

        SatelliteAssemblies.Add(cultureName, assembly);
        ResourceNames.Add(cultureName, new HashSet<string>(assembly.GetManifestResourceNames()));
        return assembly;
    }
}

我还需要提取一些反射shenanigans以使ResourceManager被设计器生成的资源类使用。应该在访问任何资源之前调用它:

private static void InjectManagerIntoType(ResourceManager manager, Type type)
{
    type
        .GetRuntimeFields()
        .First(m => m.Name == "resourceMan")
        .SetValue(null, manager);
}