From f2d1844b9b66bbad434ac8e35fc2831aead18595 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Fri, 14 Oct 2022 14:48:11 +0200 Subject: [PATCH 1/3] Collecting new language files from different sources --- .../LocalizedTextServiceFileSources.cs | 24 ++++++++++++----- ...lizedTextServiceSupplementaryFileSource.cs | 15 ++++++++++- .../UmbracoBuilder.LocalizedText.cs | 27 +++++++++++-------- 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs index 26a2e9fb60ca..aa105b96d9a5 100644 --- a/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs @@ -1,4 +1,5 @@ using System.Globalization; +using System.Text.RegularExpressions; using System.Xml; using System.Xml.Linq; using Microsoft.Extensions.FileProviders; @@ -179,7 +180,18 @@ private IEnumerable 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 + var newLangFiles = _supplementFileSources.Where(x => !x.FileInfo.Name.Contains("user") && x.FileInfo.Name.EndsWith(".xml")); + + foreach (var item in newLangFiles) + { + result.Add(item.FileInfo); + } } if (_directoryContents.Exists) @@ -236,8 +248,8 @@ private void MergeSupplementaryFiles(CultureInfo culture, XDocument xMasterDoc) // now load in supplementary IEnumerable 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) @@ -246,16 +258,16 @@ private void MergeSupplementaryFiles(CultureInfo culture, XDocument xMasterDoc) foreach (LocalizedTextServiceSupplementaryFileSource supplementaryFile in found) { - using (FileStream fs = supplementaryFile.File.OpenRead()) + using (var 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; } diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceSupplementaryFileSource.cs b/src/Umbraco.Core/Services/LocalizedTextServiceSupplementaryFileSource.cs index cff9a55234b9..3ada83dc3c27 100644 --- a/src/Umbraco.Core/Services/LocalizedTextServiceSupplementaryFileSource.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceSupplementaryFileSource.cs @@ -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; } } diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs index 54e25240e0e7..ceb68956cad6 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs @@ -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; @@ -42,20 +44,24 @@ private static IEnumerable GetSuppl // gets all langs files in /app_plugins real or virtual locations IEnumerable 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 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(); + + foreach (IFileInfo langFileSource in contentFileProvider.GetDirectoryContents(userConfigLangFolder).Where(x => x.IsDirectory && x.Name.InvariantEquals("lang"))) + { + foreach (IFileInfo langFile in contentFileProvider.GetDirectoryContents($"{userConfigLangFolder}/{langFileSource.Name}").Where(x => x.Name.InvariantEndsWith(".xml") && x.PhysicalPath != null)) + { + configLangFileSources.Add(new LocalizedTextServiceSupplementaryFileSource(langFile, true)); + } + } return localPluginFileSources .Concat(pluginLangFileSources) - .Concat(userLangFileSources); + .Concat(configLangFileSources); } @@ -83,13 +89,12 @@ private static IEnumerable GetPlugi foreach (var langFolder in GetLangFolderPaths(fileProvider, pluginFolderPath)) { // request all the files out of the path, these will have physicalPath set. - IEnumerable localizationFiles = fileProvider + IEnumerable 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); } From 4a65bd99b269dec6a4f17cd12256b40b24e89ef1 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Mon, 17 Oct 2022 11:24:38 +0200 Subject: [PATCH 2/3] Apply suggestions from review --- .../Services/LocalizedTextServiceFileSources.cs | 13 +++++-------- .../UmbracoBuilder.LocalizedText.cs | 12 +++++++++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs index aa105b96d9a5..ea75f369afa1 100644 --- a/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs @@ -167,7 +167,7 @@ public LocalizedTextServiceFileSources(ILogger } /// - /// returns all xml sources for all culture files found in the folder + /// Returns all xml sources for all culture files found in the folder. /// /// public IDictionary> GetXmlSources() => _xmlSources.Value; @@ -186,12 +186,9 @@ private IEnumerable GetLanguageFiles() if (_supplementFileSources is not null) { // Get only the .xml files and filter out the user defined language files (*.user.xml) that overwrite the default - var newLangFiles = _supplementFileSources.Where(x => !x.FileInfo.Name.Contains("user") && x.FileInfo.Name.EndsWith(".xml")); - - foreach (var item in newLangFiles) - { - result.Add(item.FileInfo); - } + result.AddRange(_supplementFileSources + .Where(x => !x.FileInfo.Name.Contains("user") && x.FileInfo.Name.EndsWith(".xml")) + .Select(x => x.FileInfo)); } if (_directoryContents.Exists) @@ -258,7 +255,7 @@ private void MergeSupplementaryFiles(CultureInfo culture, XDocument xMasterDoc) foreach (LocalizedTextServiceSupplementaryFileSource supplementaryFile in found) { - using (var stream = supplementaryFile.FileInfo.CreateReadStream()) + using (Stream stream = supplementaryFile.FileInfo.CreateReadStream()) { XDocument xChildDoc; try diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs index ceb68956cad6..60a946a553d0 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs @@ -50,11 +50,17 @@ private static IEnumerable GetSuppl var configLangFileSources = new List(); - foreach (IFileInfo langFileSource in contentFileProvider.GetDirectoryContents(userConfigLangFolder).Where(x => x.IsDirectory && x.Name.InvariantEquals("lang"))) + foreach (IFileInfo langFileSource in contentFileProvider.GetDirectoryContents(userConfigLangFolder)) { - foreach (IFileInfo langFile in contentFileProvider.GetDirectoryContents($"{userConfigLangFolder}/{langFileSource.Name}").Where(x => x.Name.InvariantEndsWith(".xml") && x.PhysicalPath != null)) + if (langFileSource.IsDirectory && langFileSource.Name.InvariantEquals("lang")) { - configLangFileSources.Add(new LocalizedTextServiceSupplementaryFileSource(langFile, true)); + 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)); + } + } } } From bf83afc4d938b5824304aa2695bb8f8fa052fc14 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Mon, 17 Oct 2022 14:22:49 +0200 Subject: [PATCH 3/3] Adding TODO for merging the language files locations to one when packages are not concerned --- .../DependencyInjection/UmbracoBuilder.Services.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index dd5b77abec5f..c9208b5bdc78 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -102,7 +102,14 @@ private static LocalizedTextServiceFileSources CreateLocalizedTextServiceFileSou IServiceProvider container) { IHostingEnvironment hostingEnvironment = container.GetRequiredService(); + + // 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(