213 lines
8.6 KiB
C#
213 lines
8.6 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System.Collections;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Reflection;
|
|
using System.Resources;
|
|
using Microsoft.Framework.Internal;
|
|
using Microsoft.Framework.Localization.Internal;
|
|
|
|
namespace Microsoft.Framework.Localization
|
|
{
|
|
/// <summary>
|
|
/// An <see cref="IStringLocalizer"/> that uses the <see cref="System.Resources.ResourceManager"/> and
|
|
/// <see cref="System.Resources.ResourceReader"/> to provide localized strings.
|
|
/// </summary>
|
|
public class ResourceManagerStringLocalizer : IStringLocalizer
|
|
{
|
|
private readonly ConcurrentDictionary<string, object> _missingManifestCache =
|
|
new ConcurrentDictionary<string, object>();
|
|
|
|
private readonly IResourceNamesCache _resourceNamesCache;
|
|
private readonly ResourceManager _resourceManager;
|
|
private readonly AssemblyWrapper _resourceAssemblyWrapper;
|
|
private readonly string _resourceBaseName;
|
|
|
|
/// <summary>
|
|
/// Creates a new <see cref="ResourceManagerStringLocalizer"/>.
|
|
/// </summary>
|
|
/// <param name="resourceManager">The <see cref="System.Resources.ResourceManager"/> to read strings from.</param>
|
|
/// <param name="resourceAssembly">The <see cref="Assembly"/> that contains the strings as embedded resources.</param>
|
|
/// <param name="baseName">The base name of the embedded resource in the <see cref="Assembly"/> that contains the strings.</param>
|
|
/// <param name="resourceNamesCache">Cache of the list of strings for a given resource assembly name.</param>
|
|
public ResourceManagerStringLocalizer(
|
|
[NotNull] ResourceManager resourceManager,
|
|
[NotNull] Assembly resourceAssembly,
|
|
[NotNull] string baseName,
|
|
[NotNull] IResourceNamesCache resourceNamesCache)
|
|
: this(resourceManager, new AssemblyWrapper(resourceAssembly), baseName, resourceNamesCache)
|
|
{
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Intended for testing purposes only.
|
|
/// </summary>
|
|
public ResourceManagerStringLocalizer(
|
|
[NotNull] ResourceManager resourceManager,
|
|
[NotNull] AssemblyWrapper resourceAssemblyWrapper,
|
|
[NotNull] string baseName,
|
|
[NotNull] IResourceNamesCache resourceNamesCache)
|
|
{
|
|
_resourceAssemblyWrapper = resourceAssemblyWrapper;
|
|
_resourceManager = resourceManager;
|
|
_resourceBaseName = baseName;
|
|
_resourceNamesCache = resourceNamesCache;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual LocalizedString this[[NotNull] string name]
|
|
{
|
|
get
|
|
{
|
|
var value = GetStringSafely(name, null);
|
|
return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual LocalizedString this[[NotNull] string name, params object[] arguments]
|
|
{
|
|
get
|
|
{
|
|
var format = GetStringSafely(name, null);
|
|
var value = string.Format(format ?? name, arguments);
|
|
return new LocalizedString(name, value, resourceNotFound: format == null);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new <see cref="ResourceManagerStringLocalizer"/> for a specific <see cref="CultureInfo"/>.
|
|
/// </summary>
|
|
/// <param name="culture">The <see cref="CultureInfo"/> to use.</param>
|
|
/// <returns>A culture-specific <see cref="ResourceManagerStringLocalizer"/>.</returns>
|
|
public IStringLocalizer WithCulture(CultureInfo culture)
|
|
{
|
|
return culture == null
|
|
? new ResourceManagerStringLocalizer(
|
|
_resourceManager,
|
|
_resourceAssemblyWrapper.Assembly,
|
|
_resourceBaseName,
|
|
_resourceNamesCache)
|
|
: new ResourceManagerWithCultureStringLocalizer(
|
|
_resourceManager,
|
|
_resourceAssemblyWrapper.Assembly,
|
|
_resourceBaseName,
|
|
_resourceNamesCache,
|
|
culture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a resource string from the <see cref="_resourceManager"/> and returns <c>null</c> instead of
|
|
/// throwing exceptions if a match isn't found.
|
|
/// </summary>
|
|
/// <param name="name">The name of the string resource.</param>
|
|
/// <param name="culture">The <see cref="CultureInfo"/> to get the string for.</param>
|
|
/// <returns>The resource string, or <c>null</c> if none was found.</returns>
|
|
protected string GetStringSafely([NotNull] string name, CultureInfo culture)
|
|
{
|
|
var cacheKey = $"name={name}&culture={(culture ?? CultureInfo.CurrentUICulture).Name}";
|
|
|
|
if (_missingManifestCache.ContainsKey(cacheKey))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
return culture == null ? _resourceManager.GetString(name) : _resourceManager.GetString(name, culture);
|
|
}
|
|
catch (MissingManifestResourceException)
|
|
{
|
|
_missingManifestCache.TryAdd(cacheKey, null);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an <see cref="IEnumerator{LocalizedString}"/> for all strings in the current culture.
|
|
/// </summary>
|
|
/// <returns>The <see cref="IEnumerator{LocalizedString}"/>.</returns>
|
|
public virtual IEnumerator<LocalizedString> GetEnumerator() => GetEnumerator(CultureInfo.CurrentUICulture);
|
|
|
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
|
|
/// <summary>
|
|
/// Returns an <see cref="IEnumerator{LocalizedString}"/> for all strings in the specified culture.
|
|
/// </summary>
|
|
/// <param name="culture">The <see cref="CultureInfo"/> to get strings for.</param>
|
|
/// <returns>The <see cref="IEnumerator{LocalizedString}"/>.</returns>
|
|
protected IEnumerator<LocalizedString> GetEnumerator([NotNull] CultureInfo culture)
|
|
{
|
|
var resourceNames = GetResourceNamesFromCultureHierarchy(culture);
|
|
|
|
foreach (var name in resourceNames)
|
|
{
|
|
var value = GetStringSafely(name, culture);
|
|
yield return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
|
|
}
|
|
}
|
|
|
|
private IEnumerable<string> GetResourceNamesFromCultureHierarchy(CultureInfo startingCulture)
|
|
{
|
|
var currentCulture = startingCulture;
|
|
var resourceNames = new HashSet<string>();
|
|
|
|
while (true)
|
|
{
|
|
try
|
|
{
|
|
var cultureResourceNames = GetResourceNamesForCulture(currentCulture);
|
|
foreach (var resourceName in cultureResourceNames)
|
|
{
|
|
resourceNames.Add(resourceName);
|
|
}
|
|
}
|
|
catch (MissingManifestResourceException) { }
|
|
|
|
if (currentCulture == currentCulture.Parent)
|
|
{
|
|
// currentCulture begat currentCulture, probably time to leave
|
|
break;
|
|
}
|
|
|
|
currentCulture = currentCulture.Parent;
|
|
}
|
|
|
|
return resourceNames;
|
|
}
|
|
|
|
private IList<string> GetResourceNamesForCulture(CultureInfo culture)
|
|
{
|
|
var resourceStreamName = _resourceBaseName;
|
|
if (!string.IsNullOrEmpty(culture.Name))
|
|
{
|
|
resourceStreamName += "." + culture.Name;
|
|
}
|
|
resourceStreamName += ".resources";
|
|
|
|
var cacheKey = $"assembly={_resourceAssemblyWrapper.FullName};resourceStreamName={resourceStreamName}";
|
|
|
|
var cultureResourceNames = _resourceNamesCache.GetOrAdd(cacheKey, key =>
|
|
{
|
|
var names = new List<string>();
|
|
using (var cultureResourceStream = _resourceAssemblyWrapper.GetManifestResourceStream(key))
|
|
using (var resources = new ResourceReader(cultureResourceStream))
|
|
{
|
|
foreach (DictionaryEntry entry in resources)
|
|
{
|
|
var resourceName = (string)entry.Key;
|
|
names.Add(resourceName);
|
|
}
|
|
}
|
|
|
|
return names;
|
|
});
|
|
|
|
return cultureResourceNames;
|
|
}
|
|
}
|
|
} |