Files
Umbraco-CMS/src/Umbraco.Core/Dictionary/UmbracoCultureDictionary.cs
Kenn Jacobsen 8caee5297b Use ISO codes instead of language IDs for fallback languages and translations (#13751)
* Use language ISO code for language fallback instead of language ID

* Remove language and language ID from dictionary item and dictionary item translation

* ADd unit test for dictionary item translation value extension

* Make the internal service implementations sealed

* Rename translation ISO code to be more explicit in its origin (Language)

* Add breaking changes suppression

* Handle save of invalid fallback iso code

* Fixed test

* Only allow non-UserCustomCulture's

* Fixed and added tests

* Rename ISO code validation method

* Fix language telemetry test (create Swedish with the correct ISO code)

---------

Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2023-02-01 09:37:37 +01:00

143 lines
5.3 KiB
C#

using System.Globalization;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Dictionary;
/// <summary>
/// A culture dictionary that uses the Umbraco ILocalizationService
/// </summary>
/// <remarks>
/// TODO: The ICultureDictionary needs to represent the 'fast' way to do dictionary item retrieval - for front-end and
/// back office.
/// The ILocalizationService is the service used for interacting with this data from the database which isn't all that
/// fast
/// (even though there is caching involved, if there's lots of dictionary items the caching is not great)
/// </remarks>
internal class DefaultCultureDictionary : ICultureDictionary
{
private readonly ILocalizationService _localizationService;
private readonly IAppCache _requestCache;
private readonly CultureInfo? _specificCulture;
/// <summary>
/// Default constructor which will use the current thread's culture
/// </summary>
/// <param name="localizationService"></param>
/// <param name="requestCache"></param>
public DefaultCultureDictionary(ILocalizationService localizationService, IAppCache requestCache)
{
_localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
_requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache));
}
/// <summary>
/// Constructor for testing to specify a static culture
/// </summary>
/// <param name="specificCulture"></param>
/// <param name="localizationService"></param>
/// <param name="requestCache"></param>
public DefaultCultureDictionary(CultureInfo specificCulture, ILocalizationService localizationService, IAppCache requestCache)
{
_localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
_requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache));
_specificCulture = specificCulture ?? throw new ArgumentNullException(nameof(specificCulture));
}
/// <summary>
/// Returns the current culture
/// </summary>
public CultureInfo Culture => _specificCulture ?? Thread.CurrentThread.CurrentUICulture;
private ILanguage? Language =>
// ensure it's stored/retrieved from request cache
// NOTE: This is no longer necessary since these are cached at the runtime level, but we can leave it here for now.
_requestCache.GetCacheItem(
typeof(DefaultCultureDictionary).Name + "Culture" + Culture.Name,
() =>
{
// find a language that matches the current culture or any of its parent cultures
CultureInfo culture = Culture;
while (culture != CultureInfo.InvariantCulture)
{
ILanguage? language = _localizationService.GetLanguageByIsoCode(culture.Name);
if (language != null)
{
return language;
}
culture = culture.Parent;
}
return null;
});
/// <summary>
/// Returns the dictionary value based on the key supplied
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public string? this[string key]
{
get
{
IDictionaryItem? found = _localizationService.GetDictionaryItemByKey(key);
if (found == null)
{
return string.Empty;
}
IDictionaryTranslation? byLang = found.Translations.FirstOrDefault(IsCurrentLanguage);
if (byLang == null)
{
return string.Empty;
}
return byLang.Value;
}
}
/// <summary>
/// Returns the child dictionary entries for a given key
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
/// <remarks>
/// NOTE: The result of this is not cached anywhere - the underlying repository does not cache
/// the child lookups because that is done by a query lookup. This method isn't used in our codebase
/// so I don't think this is a performance issue but if devs are using this it could be optimized here.
/// </remarks>
public IDictionary<string, string> GetChildren(string key)
{
var result = new Dictionary<string, string>();
IDictionaryItem? found = _localizationService.GetDictionaryItemByKey(key);
if (found == null)
{
return result;
}
IEnumerable<IDictionaryItem>? children = _localizationService.GetDictionaryItemChildren(found.Key);
if (children == null)
{
return result;
}
foreach (IDictionaryItem dictionaryItem in children)
{
IDictionaryTranslation? byLang = dictionaryItem.Translations.FirstOrDefault(IsCurrentLanguage);
if (byLang != null && dictionaryItem.ItemKey is not null && byLang.Value is not null)
{
result.Add(dictionaryItem.ItemKey, byLang.Value);
}
}
return result;
}
private bool IsCurrentLanguage(IDictionaryTranslation translation) => translation.LanguageIsoCode.Equals(Language?.IsoCode);
}