Move services infra -> core
This commit is contained in:
258
src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs
Normal file
258
src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes the XDocument sources from files for the default localization text service and ensure caching is taken care of
|
||||
/// </summary>
|
||||
public class LocalizedTextServiceFileSources
|
||||
{
|
||||
private readonly ILogger<LocalizedTextServiceFileSources> _logger;
|
||||
private readonly IAppPolicyCache _cache;
|
||||
private readonly IEnumerable<LocalizedTextServiceSupplementaryFileSource> _supplementFileSources;
|
||||
private readonly DirectoryInfo _fileSourceFolder;
|
||||
|
||||
// TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :(
|
||||
private readonly Dictionary<string, CultureInfo> _twoLetterCultureConverter = new Dictionary<string, CultureInfo>();
|
||||
|
||||
private readonly Lazy<Dictionary<CultureInfo, Lazy<XDocument>>> _xmlSources;
|
||||
|
||||
/// <summary>
|
||||
/// This is used to configure the file sources with the main file sources shipped with Umbraco and also including supplemental/plugin based
|
||||
/// localization files. The supplemental files will be loaded in and merged in after the primary files.
|
||||
/// The supplemental files must be named with the 4 letter culture name with a hyphen such as : en-AU.xml
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="cache"></param>
|
||||
/// <param name="fileSourceFolder"></param>
|
||||
/// <param name="supplementFileSources"></param>
|
||||
public LocalizedTextServiceFileSources(
|
||||
ILogger<LocalizedTextServiceFileSources> logger,
|
||||
AppCaches appCaches,
|
||||
DirectoryInfo fileSourceFolder,
|
||||
IEnumerable<LocalizedTextServiceSupplementaryFileSource> supplementFileSources)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
if (appCaches == null) throw new ArgumentNullException("cache");
|
||||
if (fileSourceFolder == null) throw new ArgumentNullException("fileSourceFolder");
|
||||
|
||||
_logger = logger;
|
||||
_cache = appCaches.RuntimeCache;
|
||||
|
||||
if (fileSourceFolder.Exists == false)
|
||||
{
|
||||
_logger.LogWarning("The folder does not exist: {FileSourceFolder}, therefore no sources will be discovered", fileSourceFolder.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_fileSourceFolder = fileSourceFolder;
|
||||
_supplementFileSources = supplementFileSources;
|
||||
}
|
||||
|
||||
//Create the lazy source for the _xmlSources
|
||||
_xmlSources = new Lazy<Dictionary<CultureInfo, Lazy<XDocument>>>(() =>
|
||||
{
|
||||
var result = new Dictionary<CultureInfo, Lazy<XDocument>>();
|
||||
|
||||
if (_fileSourceFolder == null) return result;
|
||||
|
||||
foreach (var fileInfo in _fileSourceFolder.GetFiles("*.xml"))
|
||||
{
|
||||
var localCopy = fileInfo;
|
||||
var filename = Path.GetFileNameWithoutExtension(localCopy.FullName).Replace("_", "-");
|
||||
|
||||
// TODO: Fix this nonsense... would have to wait until v8 to store the language files with their correct
|
||||
// names instead of storing them as 2 letters but actually having a 4 letter culture. So now, we
|
||||
// need to check if the file is 2 letters, then open it to try to find it's 4 letter culture, then use that
|
||||
// if it's successful. We're going to assume (though it seems assuming in the legacy logic is never a great idea)
|
||||
// that any 4 letter file is named with the actual culture that it is!
|
||||
CultureInfo culture = null;
|
||||
if (filename.Length == 2)
|
||||
{
|
||||
//we need to open the file to see if we can read it's 'real' culture, we'll use XmlReader since we don't
|
||||
//want to load in the entire doc into mem just to read a single value
|
||||
using (var fs = fileInfo.OpenRead())
|
||||
using (var reader = XmlReader.Create(fs))
|
||||
{
|
||||
if (reader.IsStartElement())
|
||||
{
|
||||
if (reader.Name == "language")
|
||||
{
|
||||
if (reader.MoveToAttribute("culture"))
|
||||
{
|
||||
var cultureVal = reader.Value;
|
||||
try
|
||||
{
|
||||
culture = CultureInfo.GetCultureInfo(cultureVal);
|
||||
//add to the tracked dictionary
|
||||
_twoLetterCultureConverter[filename] = culture;
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
_logger.LogWarning("The culture {CultureValue} found in the file {CultureFile} is not a valid culture", cultureVal, fileInfo.FullName);
|
||||
//If the culture in the file is invalid, we'll just hope the file name is a valid culture below, otherwise
|
||||
// an exception will be thrown.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (culture == null)
|
||||
{
|
||||
culture = CultureInfo.GetCultureInfo(filename);
|
||||
}
|
||||
|
||||
//get the lazy value from cache
|
||||
result[culture] = new Lazy<XDocument>(() => _cache.GetCacheItem<XDocument>(
|
||||
string.Format("{0}-{1}", typeof(LocalizedTextServiceFileSources).Name, culture.Name), () =>
|
||||
{
|
||||
XDocument xdoc;
|
||||
|
||||
//load in primary
|
||||
using (var fs = localCopy.OpenRead())
|
||||
{
|
||||
xdoc = XDocument.Load(fs);
|
||||
}
|
||||
|
||||
//load in supplementary
|
||||
MergeSupplementaryFiles(culture, xdoc);
|
||||
|
||||
return xdoc;
|
||||
}, isSliding: true, timeout: TimeSpan.FromMinutes(10), dependentFiles: new[] { localCopy.FullName }));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public LocalizedTextServiceFileSources(ILogger<LocalizedTextServiceFileSources> logger, AppCaches appCaches, DirectoryInfo fileSourceFolder)
|
||||
: this(logger, appCaches, fileSourceFolder, Enumerable.Empty<LocalizedTextServiceSupplementaryFileSource>())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// returns all xml sources for all culture files found in the folder
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IDictionary<CultureInfo, Lazy<XDocument>> GetXmlSources()
|
||||
{
|
||||
return _xmlSources.Value;
|
||||
}
|
||||
|
||||
// TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :(
|
||||
public Attempt<CultureInfo> TryConvert2LetterCultureTo4Letter(string twoLetterCulture)
|
||||
{
|
||||
if (twoLetterCulture.Length != 2) return Attempt<CultureInfo>.Fail();
|
||||
|
||||
//This needs to be resolved before continuing so that the _twoLetterCultureConverter cache is initialized
|
||||
var resolved = _xmlSources.Value;
|
||||
|
||||
return _twoLetterCultureConverter.ContainsKey(twoLetterCulture)
|
||||
? Attempt.Succeed(_twoLetterCultureConverter[twoLetterCulture])
|
||||
: Attempt<CultureInfo>.Fail();
|
||||
}
|
||||
|
||||
// TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :(
|
||||
public Attempt<string> TryConvert4LetterCultureTo2Letter(CultureInfo culture)
|
||||
{
|
||||
if (culture == null) throw new ArgumentNullException("culture");
|
||||
|
||||
//This needs to be resolved before continuing so that the _twoLetterCultureConverter cache is initialized
|
||||
var resolved = _xmlSources.Value;
|
||||
|
||||
return _twoLetterCultureConverter.Values.Contains(culture)
|
||||
? Attempt.Succeed(culture.Name.Substring(0, 2))
|
||||
: Attempt<string>.Fail();
|
||||
}
|
||||
|
||||
private void MergeSupplementaryFiles(CultureInfo culture, XDocument xMasterDoc)
|
||||
{
|
||||
if (xMasterDoc.Root == null) return;
|
||||
if (_supplementFileSources != null)
|
||||
{
|
||||
//now load in supplementary
|
||||
var found = _supplementFileSources.Where(x =>
|
||||
{
|
||||
var extension = Path.GetExtension(x.File.FullName);
|
||||
var fileCultureName = Path.GetFileNameWithoutExtension(x.File.FullName).Replace("_", "-").Replace(".user", "");
|
||||
return extension.InvariantEquals(".xml") && (
|
||||
fileCultureName.InvariantEquals(culture.Name)
|
||||
|| fileCultureName.InvariantEquals(culture.TwoLetterISOLanguageName)
|
||||
);
|
||||
});
|
||||
|
||||
foreach (var supplementaryFile in found)
|
||||
{
|
||||
using (var fs = supplementaryFile.File.OpenRead())
|
||||
{
|
||||
XDocument xChildDoc;
|
||||
try
|
||||
{
|
||||
xChildDoc = XDocument.Load(fs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not load file into XML {File}", supplementaryFile.File.FullName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (xChildDoc.Root == null || xChildDoc.Root.Name != "language") continue;
|
||||
foreach (var xArea in xChildDoc.Root.Elements("area")
|
||||
.Where(x => ((string)x.Attribute("alias")).IsNullOrWhiteSpace() == false))
|
||||
{
|
||||
var areaAlias = (string)xArea.Attribute("alias");
|
||||
|
||||
var areaFound = xMasterDoc.Root.Elements("area").FirstOrDefault(x => ((string)x.Attribute("alias")) == areaAlias);
|
||||
if (areaFound == null)
|
||||
{
|
||||
//add the whole thing
|
||||
xMasterDoc.Root.Add(xArea);
|
||||
}
|
||||
else
|
||||
{
|
||||
MergeChildKeys(xArea, areaFound, supplementaryFile.OverwriteCoreKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MergeChildKeys(XElement source, XElement destination, bool overwrite)
|
||||
{
|
||||
if (destination == null) throw new ArgumentNullException("destination");
|
||||
if (source == null) throw new ArgumentNullException("source");
|
||||
|
||||
//merge in the child elements
|
||||
foreach (var key in source.Elements("key")
|
||||
.Where(x => ((string)x.Attribute("alias")).IsNullOrWhiteSpace() == false))
|
||||
{
|
||||
var keyAlias = (string)key.Attribute("alias");
|
||||
var keyFound = destination.Elements("key").FirstOrDefault(x => ((string)x.Attribute("alias")) == keyAlias);
|
||||
if (keyFound == null)
|
||||
{
|
||||
//append, it doesn't exist
|
||||
destination.Add(key);
|
||||
}
|
||||
else if (overwrite)
|
||||
{
|
||||
//overwrite
|
||||
keyFound.Value = key.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user