Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Globalization;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using Microsoft.Extensions.FileProviders;
Expand Down Expand Up @@ -166,7 +167,7 @@ public LocalizedTextServiceFileSources(ILogger<LocalizedTextServiceFileSources>
}

/// <summary>
/// returns all xml sources for all culture files found in the folder
/// Returns all xml sources for all culture files found in the folder.
/// </summary>
/// <returns></returns>
public IDictionary<CultureInfo, Lazy<XDocument>> GetXmlSources() => _xmlSources.Value;
Expand All @@ -179,7 +180,15 @@ private IEnumerable<IFileInfo> GetLanguageFiles()
{
result.AddRange(
new PhysicalDirectoryContents(_fileSourceFolder.FullName)
.Where(x => !x.IsDirectory && x.Name.EndsWith(".xml")));
.Where(x => !x.IsDirectory && !x.Name.Contains("user") && x.Name.EndsWith(".xml"))); // Filter out *.user.xml
}

if (_supplementFileSources is not null)
{
// Get only the .xml files and filter out the user defined language files (*.user.xml) that overwrite the default
result.AddRange(_supplementFileSources
.Where(x => !x.FileInfo.Name.Contains("user") && x.FileInfo.Name.EndsWith(".xml"))
.Select(x => x.FileInfo));
}

if (_directoryContents.Exists)
Expand Down Expand Up @@ -236,8 +245,8 @@ private void MergeSupplementaryFiles(CultureInfo culture, XDocument xMasterDoc)
// now load in supplementary
IEnumerable<LocalizedTextServiceSupplementaryFileSource> found = _supplementFileSources.Where(x =>
{
var extension = Path.GetExtension(x.File.FullName);
var fileCultureName = Path.GetFileNameWithoutExtension(x.File.FullName).Replace("_", "-")
var extension = Path.GetExtension(x.FileInfo.Name);
var fileCultureName = Path.GetFileNameWithoutExtension(x.FileInfo.Name).Replace("_", "-")
.Replace(".user", string.Empty);
return extension.InvariantEquals(".xml") && (
fileCultureName.InvariantEquals(culture.Name)
Expand All @@ -246,16 +255,16 @@ private void MergeSupplementaryFiles(CultureInfo culture, XDocument xMasterDoc)

foreach (LocalizedTextServiceSupplementaryFileSource supplementaryFile in found)
{
using (FileStream fs = supplementaryFile.File.OpenRead())
using (Stream stream = supplementaryFile.FileInfo.CreateReadStream())
{
XDocument xChildDoc;
try
{
xChildDoc = XDocument.Load(fs);
xChildDoc = XDocument.Load(stream);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not load file into XML {File}", supplementaryFile.File.FullName);
_logger.LogError(ex, "Could not load file into XML {File}", supplementaryFile.FileInfo.Name);
continue;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Physical;

namespace Umbraco.Cms.Core.Services;

public class LocalizedTextServiceSupplementaryFileSource
{
[Obsolete("Use other ctor. Will be removed in Umbraco 12")]
public LocalizedTextServiceSupplementaryFileSource(FileInfo file, bool overwriteCoreKeys)
: this(new PhysicalFileInfo(file), overwriteCoreKeys)
{
File = file ?? throw new ArgumentNullException("file");
}

public LocalizedTextServiceSupplementaryFileSource(IFileInfo file, bool overwriteCoreKeys)
{
FileInfo = file ?? throw new ArgumentNullException(nameof(file));
File = file is PhysicalFileInfo ? new FileInfo(file.PhysicalPath) : null!;
OverwriteCoreKeys = overwriteCoreKeys;
}

[Obsolete("Use FileInfo instead. Will be removed in Umbraco 12")]
public FileInfo File { get; }

public IFileInfo FileInfo { get; }

public bool OverwriteCoreKeys { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,14 @@ private static LocalizedTextServiceFileSources CreateLocalizedTextServiceFileSou
IServiceProvider container)
{
IHostingEnvironment hostingEnvironment = container.GetRequiredService<IHostingEnvironment>();

// TODO: (for >= v13) Rethink whether all language files (.xml and .user.xml) should be located in ~/config/lang
// instead of ~/umbraco/config/lang and ~/config/lang.
// Currently when extending Umbraco, a new language file that the backoffice will be available in, should be placed
// in ~/umbraco/config/lang, while 'user' translation files for overrides are in ~/config/lang (according to our docs).
// Such change will be breaking and we would need to document this clearly.
var subPath = WebPath.Combine(Constants.SystemDirectories.Umbraco, "config", "lang");

var mainLangFolder = new DirectoryInfo(hostingEnvironment.MapPathContentRoot(subPath));

return new LocalizedTextServiceFileSources(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Services;
Expand Down Expand Up @@ -42,20 +44,30 @@ private static IEnumerable<LocalizedTextServiceSupplementaryFileSource> GetSuppl
// gets all langs files in /app_plugins real or virtual locations
IEnumerable<LocalizedTextServiceSupplementaryFileSource> pluginLangFileSources = GetPluginLanguageFileSources(webFileProvider, Cms.Core.Constants.SystemDirectories.AppPlugins, false);

// user defined langs that overwrite the default, these should not be used by plugin creators
// user defined language files that overwrite the default, these should not be used by plugin creators
var userConfigLangFolder = Cms.Core.Constants.SystemDirectories.Config
.TrimStart(Cms.Core.Constants.CharArrays.Tilde);

IEnumerable<LocalizedTextServiceSupplementaryFileSource> userLangFileSources = contentFileProvider.GetDirectoryContents(userConfigLangFolder)
.Where(x => x.IsDirectory && x.Name.InvariantEquals("lang"))
.Select(x => new DirectoryInfo(x.PhysicalPath))
.SelectMany(x => x.GetFiles("*.user.xml", SearchOption.TopDirectoryOnly))
.Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true));
var configLangFileSources = new List<LocalizedTextServiceSupplementaryFileSource>();

foreach (IFileInfo langFileSource in contentFileProvider.GetDirectoryContents(userConfigLangFolder))
{
if (langFileSource.IsDirectory && langFileSource.Name.InvariantEquals("lang"))
{
foreach (IFileInfo langFile in contentFileProvider.GetDirectoryContents($"{userConfigLangFolder}/{langFileSource.Name}"))
{
if (langFile.Name.InvariantEndsWith(".xml") && langFile.PhysicalPath is not null)
{
configLangFileSources.Add(new LocalizedTextServiceSupplementaryFileSource(langFile, true));
}
}
}
}

return
localPluginFileSources
.Concat(pluginLangFileSources)
.Concat(userLangFileSources);
.Concat(configLangFileSources);
}


Expand Down Expand Up @@ -83,13 +95,12 @@ private static IEnumerable<LocalizedTextServiceSupplementaryFileSource> GetPlugi
foreach (var langFolder in GetLangFolderPaths(fileProvider, pluginFolderPath))
{
// request all the files out of the path, these will have physicalPath set.
IEnumerable<FileInfo> localizationFiles = fileProvider
IEnumerable<IFileInfo> localizationFiles = fileProvider
.GetDirectoryContents(langFolder)
.Where(x => !string.IsNullOrEmpty(x.PhysicalPath))
.Where(x => x.Name.InvariantEndsWith(".xml"))
.Select(x => new FileInfo(x.PhysicalPath));
.Where(x => x.Name.InvariantEndsWith(".xml"));

foreach (FileInfo file in localizationFiles)
foreach (IFileInfo file in localizationFiles)
{
yield return new LocalizedTextServiceSupplementaryFileSource(file, overwriteCoreKeys);
}
Expand Down