Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;

namespace Microsoft.TemplateEngine.Abstractions
{
/// <summary>
/// Model type that contains the string-replace operations to be performed
/// on a file in order to localize it.
/// </summary>
public interface IFileLocalizationModel
{
/// <summary>
/// Gets the globbing pattern to determine the files
/// that these localizations will be applied to.
/// </summary>
string File { get; }

/// <summary>
/// Gets the dictionary containing the localized strings as values
/// where the keys are the string to be replaced.
/// </summary>
IReadOnlyDictionary<string, string> Localizations { get; }
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@

Microsoft.TemplateEngine.Abstractions.IFileLocalizationModel
~Microsoft.TemplateEngine.Abstractions.IFileLocalizationModel.File.get -> string
~Microsoft.TemplateEngine.Abstractions.IFileLocalizationModel.Localizations.get -> System.Collections.Generic.IReadOnlyDictionary<string, string>
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public interface IGlobalRunSpec

IReadOnlyList<KeyValuePair<IPathMatcher, IRunSpec>> Special { get; }

IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> LocalizationOperations { get; }

IReadOnlyList<string> IgnoreFileNames { get; }

bool TryGetTargetRelPath(string sourceRelPath, out string targetRelPath);
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@

~Microsoft.TemplateEngine.Core.Contracts.IGlobalRunSpec.LocalizationOperations.get -> System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IReadOnlyDictionary<string, string>>
22 changes: 20 additions & 2 deletions src/Microsoft.TemplateEngine.Core/Util/Orchestrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Abstractions.Mount;
using Microsoft.TemplateEngine.Core.Contracts;
using Microsoft.TemplateEngine.Core.Operations;
using Microsoft.TemplateEngine.Utils;

namespace Microsoft.TemplateEngine.Core.Util
Expand Down Expand Up @@ -252,7 +253,8 @@ private void RunInternal(IEngineEnvironmentSettings environmentSettings, IDirect
}
else if (!copy)
{
ProcessFile(file, sourceRel, targetDir, spec, fallback, fileGlobProcessors);
spec.LocalizationOperations.TryGetValue(sourceRel, out var localizedReplacements);
ProcessFile(file, sourceRel, targetDir, spec, fallback, localizedReplacements, fileGlobProcessors);
}
else
{
Expand All @@ -272,14 +274,30 @@ private void RunInternal(IEngineEnvironmentSettings environmentSettings, IDirect
}
}

private void ProcessFile(IFile sourceFile, string sourceRel, string targetDir, IGlobalRunSpec spec, IProcessor fallback, IEnumerable<KeyValuePair<IPathMatcher, IProcessor>> fileGlobProcessors)
private void ProcessFile(
IFile sourceFile,
string sourceRel,
string targetDir,
IGlobalRunSpec spec,
IProcessor fallback,
IReadOnlyDictionary<string, string> localizedReplacementsForFile,
IEnumerable<KeyValuePair<IPathMatcher, IProcessor>> fileGlobProcessors)
{
IProcessor runner = fileGlobProcessors.FirstOrDefault(x => x.Key.IsMatch(sourceRel)).Value ?? fallback;
if (runner == null)
{
throw new InvalidOperationException("At least one of [runner] or [fallback] cannot be null");
}

if (localizedReplacementsForFile != null)
{
List<IOperationProvider> fileLocalizationOperations = localizedReplacementsForFile
.Select(l => new Replacement(l.Key.TokenConfig(), l.Value, null, true))
.Cast<IOperationProvider>()
.ToList();
runner = runner.CloneAndAppendOperations(fileLocalizationOperations);
}

if (!spec.TryGetTargetRelPath(sourceRel, out string targetRel))
{
targetRel = sourceRel;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ internal GlobalRunSpec(
IVariableCollection variables,
IGlobalRunConfig globalConfig,
IReadOnlyList<KeyValuePair<string, IGlobalRunConfig>> fileGlobConfigs,
IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> localizationOperations,
IReadOnlyList<string> ignoreFileNames)
{
EnsureOperationConfigs(componentManager);

RootVariableCollection = variables;
LocalizationOperations = localizationOperations;
IgnoreFileNames = ignoreFileNames;
Operations = ResolveOperations(globalConfig, templateRoot, variables, parameters);
List<KeyValuePair<IPathMatcher, IRunSpec>> specials = new List<KeyValuePair<IPathMatcher, IRunSpec>>();
Expand Down Expand Up @@ -72,6 +74,8 @@ internal GlobalRunSpec(

public IReadOnlyList<KeyValuePair<IPathMatcher, IRunSpec>> Special { get; }

public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> LocalizationOperations { get; }

public IReadOnlyList<string> IgnoreFileNames { get; }

internal IReadOnlyDictionary<string, string> Rename { get; private set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,10 @@ internal interface ILocalizationModel
/// The keys represent the id of the post actions.
/// </summary>
IReadOnlyDictionary<string, IPostActionLocalizationModel> PostActions { get; }

/// <summary>
/// Gets the localizated string replacements to be applied to contents of files.
/// </summary>
IReadOnlyList<IFileLocalizationModel> LocalizableReplacements { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ internal interface IRunnableProjectConfig

IReadOnlyList<KeyValuePair<string, IGlobalRunConfig>> SpecialOperationConfig { get; }

IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> LocalizationOperations { get; }

IGlobalRunConfig OperationConfig { get; }

IReadOnlyList<FileSourceMatchInfo> Sources { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ public LocalizationModel(
string? description,
string? author,
IReadOnlyDictionary<string, IParameterSymbolLocalizationModel> parameterSymbols,
IReadOnlyDictionary<string, IPostActionLocalizationModel> postActions)
IReadOnlyDictionary<string, IPostActionLocalizationModel> postActions,
IReadOnlyList<IFileLocalizationModel> fileLocalizations)
{
Name = name;
Description = description;
Author = author;
ParameterSymbols = parameterSymbols ?? throw new ArgumentNullException(nameof(parameterSymbols));
PostActions = postActions ?? throw new ArgumentNullException(nameof(postActions));
LocalizableReplacements = fileLocalizations ?? throw new ArgumentNullException(nameof(fileLocalizations));
}

/// <inheritdoc/>
Expand All @@ -39,5 +41,8 @@ public LocalizationModel(

/// <inheritdoc/>
public IReadOnlyDictionary<string, IPostActionLocalizationModel> PostActions { get; }

/// <inheritdoc/>
public IReadOnlyList<IFileLocalizationModel> LocalizableReplacements { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,15 @@ public static ILocalizationModel Deserialize(IFile file)
var symbols = LoadSymbolModels(localizedStrings);
var postActions = LoadPostActionModels(localizedStrings);

// TODO load localized replacements: "localizedReplacements/globbing_string/symbol_name": "localized value"

return new LocalizationModel(
name: localizedStrings.FirstOrDefault(s => s.Key == "name").Value,
description: localizedStrings.FirstOrDefault(s => s.Key == "description").Value,
author: localizedStrings.FirstOrDefault(s => s.Key == "author").Value,
symbols,
postActions);
postActions,
pass_localized_replacements_here);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public Task<ICreationResult> CreateAsync(
IOrchestrator2 basicOrchestrator = new Core.Util.Orchestrator();
RunnableProjectOrchestrator orchestrator = new RunnableProjectOrchestrator(basicOrchestrator);

GlobalRunSpec runSpec = new GlobalRunSpec(templateData.TemplateSourceRoot, environmentSettings.Components, parameters, variables, template.Config.OperationConfig, template.Config.SpecialOperationConfig, template.Config.IgnoreFileNames);
GlobalRunSpec runSpec = new GlobalRunSpec(templateData.TemplateSourceRoot, environmentSettings.Components, parameters, variables, template.Config.OperationConfig, template.Config.SpecialOperationConfig, template.Config.LocalizationOperations, template.Config.IgnoreFileNames);

foreach (FileSourceMatchInfo source in template.Config.Sources)
{
Expand Down Expand Up @@ -96,7 +96,7 @@ public Task<ICreationEffects> GetCreationEffectsAsync(
IOrchestrator2 basicOrchestrator = new Core.Util.Orchestrator();
RunnableProjectOrchestrator orchestrator = new RunnableProjectOrchestrator(basicOrchestrator);

GlobalRunSpec runSpec = new GlobalRunSpec(templateData.TemplateSourceRoot, environmentSettings.Components, parameters, variables, template.Config.OperationConfig, template.Config.SpecialOperationConfig, template.Config.IgnoreFileNames);
GlobalRunSpec runSpec = new GlobalRunSpec(templateData.TemplateSourceRoot, environmentSettings.Components, parameters, variables, template.Config.OperationConfig, template.Config.SpecialOperationConfig, template.Config.LocalizationOperations, template.Config.IgnoreFileNames);
List<IFileChange2> changes = new List<IFileChange2>();

foreach (FileSourceMatchInfo source in template.Config.Sources)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ internal SimpleConfigModel(IEngineEnvironmentSettings environmentSettings, JObje
src.Exclude = item.Get<JToken>(nameof(src.Exclude));
src.Include = item.Get<JToken>(nameof(src.Include));
src.Condition = item.ToString(nameof(src.Condition));
src.Rename = item.Get<JObject>(nameof(src.Rename)).ToStringDictionary().ToDictionary(x => x.Key, x => x.Value);
src.Rename = item.Get<JObject>(nameof(src.Rename)).ToStringDictionary();

List<SourceModifier> modifiers = new List<SourceModifier>();
src.Modifiers = modifiers;
Expand Down Expand Up @@ -183,7 +183,7 @@ internal SimpleConfigModel(IEngineEnvironmentSettings environmentSettings, JObje
}
}

_postActions = RunnableProjects.PostActionModel.LoadListFromJArray(source.Get<JArray>("PostActions"), _logger, filename);
_postActions = PostActionModel.LoadListFromJArray(source.Get<JArray>("PostActions"), _logger, filename);
PrimaryOutputs = CreationPathModel.ListFromJArray(source.Get<JArray>(nameof(PrimaryOutputs)));

// Custom operations at the global level
Expand All @@ -204,6 +204,18 @@ internal SimpleConfigModel(IEngineEnvironmentSettings environmentSettings, JObje
}

_specialCustomSetup = specialCustomSetup;

// Localized string replacements. Template config file only contains the authoring language replacements. The rest will come from localization files.
Dictionary<string, JToken> localizedReplacementFiles = source.ToJTokenDictionary(StringComparer.OrdinalIgnoreCase, "localizedReplacements");
Dictionary<string, IReadOnlyDictionary<string, string>> localizations = new Dictionary<string, IReadOnlyDictionary<string, string>>();
if (localizedReplacementFiles != null)
{
foreach (var fileTokenPair in localizedReplacementFiles)
{
localizations.Add(fileTokenPair.Key, fileTokenPair.Value.ToStringDictionary(StringComparer.OrdinalIgnoreCase));
}
}
LocalizationOperations = localizations;
}

internal SimpleConfigModel(IFile templateFile, ISimpleConfigModifiers configModifiers = null)
Expand Down Expand Up @@ -254,6 +266,14 @@ public IReadOnlyList<string> IgnoreFileNames
}
}

/// <summary>
/// Gets the localized replacement strings for each file.
/// Each key of the outter dictionary is a globbing pattern that defines which files
/// will be considered for replacement. Each value is a dictionary mapping the
/// token to be replaced to the new value.
/// </summary>
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> LocalizationOperations { get; private set; }

IReadOnlyList<string> IRunnableProjectConfig.Classifications => Classifications;

IReadOnlyList<FileSourceMatchInfo> IRunnableProjectConfig.Sources
Expand Down Expand Up @@ -652,6 +672,40 @@ internal void Localize(ILocalizationModel locModel)
postAction.Localize(postActionLocModel, _logger);
}
}

if (locModel.LocalizableReplacements != null)
{
// Recreate the data in the localized way.
Dictionary<string, IReadOnlyDictionary<string, string>> newLocalizedOperations = new Dictionary<string, IReadOnlyDictionary<string, string>>();

// Get the localized replacements from the model.
foreach (var fileLocalizations in locModel.LocalizableReplacements)
{
// Find the matching file listed from the template config file
if (!LocalizationOperations.TryGetValue(fileLocalizations.File, out var replacements))
{
// No data available in template config file for this localized data.
continue;
}

Dictionary<string, string> localizedReplacementsForFile = new Dictionary<string, string>();
foreach (var replacement in replacements)
{
// Find the matching localization for the replacement
if (!fileLocalizations.Localizations.TryGetValue(replacement.Key, out string localizedString))
{
// No loc data for this token. Use original value.
localizedReplacementsForFile[replacement.Key] = replacement.Value;
continue;
}

localizedReplacementsForFile[replacement.Key] = localizedString;
}
newLocalizedOperations[fileLocalizations.File] = localizedReplacementsForFile;
}

LocalizationOperations = newLocalizedOperations;
}
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Shared/JExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ internal static IEnumerable<JProperty> PropertiesOf(this JToken? token, string?
return res as T;
}

internal static IReadOnlyDictionary<string, string> ToStringDictionary(this JToken token, StringComparer? comparer = null, string? propertyName = null)
internal static Dictionary<string, string> ToStringDictionary(this JToken token, StringComparer? comparer = null, string? propertyName = null)
{
Dictionary<string, string> result = new Dictionary<string, string>(comparer ?? StringComparer.Ordinal);

Expand All @@ -199,7 +199,7 @@ internal static IReadOnlyDictionary<string, string> ToStringDictionary(this JTok
}

// Leaves the values as JTokens.
internal static IReadOnlyDictionary<string, JToken> ToJTokenDictionary(this JToken token, StringComparer? comparaer = null, string? propertyName = null)
internal static Dictionary<string, JToken> ToJTokenDictionary(this JToken token, StringComparer? comparaer = null, string? propertyName = null)
{
Dictionary<string, JToken> result = new Dictionary<string, JToken>(comparaer ?? StringComparer.Ordinal);

Expand Down
4 changes: 2 additions & 2 deletions test/Microsoft.TemplateEngine.Mocks/MockGlobalRunSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public MockGlobalRunSpec()
CopyOnly = new List<IPathMatcher>();
Operations = new List<IOperationProvider>();
Special = new List<KeyValuePair<IPathMatcher, IRunSpec>>();
LocalizationOperations = new Dictionary<string, IReadOnlyList<IOperationProvider>>();
LocalizationOperations = new Dictionary<string, IReadOnlyDictionary<string, string>>();
Rename = new Dictionary<string, string>();
IgnoreFileNames = new[] { "-.-", "_._" };
}
Expand All @@ -32,7 +32,7 @@ public MockGlobalRunSpec()

public IReadOnlyList<KeyValuePair<IPathMatcher, IRunSpec>> Special { get; set; }

public IReadOnlyDictionary<string, IReadOnlyList<IOperationProvider>> LocalizationOperations { get; set; }
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> LocalizationOperations { get; set; }

public IReadOnlyList<string> IgnoreFileNames { get; set; }

Expand Down