// 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 { /// /// An that uses the and /// to provide localized strings. /// public class ResourceManagerStringLocalizer : IStringLocalizer { private readonly ConcurrentDictionary _missingManifestCache = new ConcurrentDictionary(); private readonly IResourceNamesCache _resourceNamesCache; private readonly ResourceManager _resourceManager; private readonly AssemblyWrapper _resourceAssemblyWrapper; private readonly string _resourceBaseName; /// /// Creates a new . /// /// The to read strings from. /// The that contains the strings as embedded resources. /// The base name of the embedded resource in the that contains the strings. /// Cache of the list of strings for a given resource assembly name. public ResourceManagerStringLocalizer( [NotNull] ResourceManager resourceManager, [NotNull] Assembly resourceAssembly, [NotNull] string baseName, [NotNull] IResourceNamesCache resourceNamesCache) : this(resourceManager, new AssemblyWrapper(resourceAssembly), baseName, resourceNamesCache) { } /// /// Intended for testing purposes only. /// public ResourceManagerStringLocalizer( [NotNull] ResourceManager resourceManager, [NotNull] AssemblyWrapper resourceAssemblyWrapper, [NotNull] string baseName, [NotNull] IResourceNamesCache resourceNamesCache) { _resourceAssemblyWrapper = resourceAssemblyWrapper; _resourceManager = resourceManager; _resourceBaseName = baseName; _resourceNamesCache = resourceNamesCache; } /// public virtual LocalizedString this[[NotNull] string name] { get { var value = GetStringSafely(name, null); return new LocalizedString(name, value ?? name, resourceNotFound: value == null); } } /// 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); } } /// /// Creates a new for a specific . /// /// The to use. /// A culture-specific . public IStringLocalizer WithCulture(CultureInfo culture) { return culture == null ? new ResourceManagerStringLocalizer( _resourceManager, _resourceAssemblyWrapper.Assembly, _resourceBaseName, _resourceNamesCache) : new ResourceManagerWithCultureStringLocalizer( _resourceManager, _resourceAssemblyWrapper.Assembly, _resourceBaseName, _resourceNamesCache, culture); } /// /// Gets a resource string from the and returns null instead of /// throwing exceptions if a match isn't found. /// /// The name of the string resource. /// The to get the string for. /// The resource string, or null if none was found. 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; } } /// /// Returns an for all strings in the current culture. /// /// The . public virtual IEnumerator GetEnumerator() => GetEnumerator(CultureInfo.CurrentUICulture); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Returns an for all strings in the specified culture. /// /// The to get strings for. /// The . protected IEnumerator 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 GetResourceNamesFromCultureHierarchy(CultureInfo startingCulture) { var currentCulture = startingCulture; var resourceNames = new HashSet(); 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 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(); 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; } } }