| File: LocalizationModelDeserializer.cs | Web Access |
| Project: src\sdk\src\TemplateEngine\Microsoft.TemplateEngine.Orchestrator.RunnableProjects\Microsoft.TemplateEngine.Orchestrator.RunnableProjects.csproj (Microsoft.TemplateEngine.Orchestrator.RunnableProjects) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Text.Json; using Microsoft.TemplateEngine.Abstractions; using Microsoft.TemplateEngine.Abstractions.Mount; using Microsoft.TemplateEngine.Orchestrator.RunnableProjects.Localization; namespace Microsoft.TemplateEngine.Orchestrator.RunnableProjects { internal static class LocalizationModelDeserializer { /// <summary> /// Character to be used when separating a key into parts. /// </summary> private const char KeySeparator = '/'; /// <summary> /// Deserializes the given localization file into an <see cref="LocalizationModel"/>. /// </summary> /// <param name="file">File to be deserialized.</param> /// <returns>loaded localization model.</returns> /// <exception cref="ArgumentNullException"><paramref name="file"/> is null.</exception> /// <exception cref="Exception">The file does not contain valid JSON data, JSON data contains element other than strings.</exception> public static LocalizationModel Deserialize(IFile file) { _ = file ?? throw new ArgumentNullException(nameof(file)); System.Text.Json.Nodes.JsonObject srcObject = file.ReadJObjectFromIFile(); List<(string Key, string Value)> localizedStrings = srcObject .Select(p => p.Value?.GetValueKind() == JsonValueKind.String ? (p.Key, p.Value.GetValue<string>()) : throw new Exception(LocalizableStrings.Authoring_InvalidJsonElementInLocalizationFile)) .ToList(); var symbols = LoadSymbolModels(localizedStrings); var postActions = LoadPostActionModels(localizedStrings); 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); } /// <summary> /// Generates parameter symbol localization models from the given localized strings. /// </summary> private static IReadOnlyDictionary<string, IParameterSymbolLocalizationModel> LoadSymbolModels(List<(string Key, string Value)> localizedStrings) { var results = new Dictionary<string, IParameterSymbolLocalizationModel>(); // Property names are in format: symbols/framework/choices/net5.0/description // Split them using '/' and store together with the localized string. IEnumerable<(IEnumerable<string> NameParts, string LocalizedString)> strings = localizedStrings .Where(s => s.Key.StartsWith("symbols" + KeySeparator)) .Select(s => (s.Key.Split(KeySeparator).AsEnumerable().Skip(1), s.Value)) .ToList(); // Group by symbol name foreach (var parameterParts in strings.GroupBy(p => p.NameParts.FirstOrDefault())) { if (string.IsNullOrEmpty(parameterParts.Key)) { // Symbol with no name. Ignore. continue; } string symbolName = parameterParts.Key; string? displayName = parameterParts.SingleOrDefault(p => p.NameParts.Skip(1).FirstOrDefault() == "displayName").LocalizedString; string? description = parameterParts.SingleOrDefault(p => p.NameParts.Skip(1).FirstOrDefault() == "description").LocalizedString; IReadOnlyDictionary<string, ParameterChoiceLocalizationModel>? choiceModels = LoadChoiceModels(parameterParts .Where(p => p.NameParts.Skip(1).FirstOrDefault() == "choices") .Select(p => (p.NameParts.Skip(2), p.LocalizedString))); ParameterSymbolLocalizationModel paramLoc = new ParameterSymbolLocalizationModel( symbolName, displayName, description, choiceModels); results[symbolName] = paramLoc; } return results; } /// <summary> /// Generates post action localization models. The given parts should begin with the choice name /// as shown below (prior parts of the name such as "symbols" and parameter name shouldn't be included). /// <list type="table"> /// <item>net5.0/displayName</item> /// <item>net5.0/description</item> /// <item>netstandard2.0/description</item> /// </list> /// </summary> private static IReadOnlyDictionary<string, ParameterChoiceLocalizationModel> LoadChoiceModels(IEnumerable<(IEnumerable<string> NameParts, string LocalizedString)> strings) { var results = new Dictionary<string, ParameterChoiceLocalizationModel>(); foreach (var choiceParts in strings.GroupBy(p => p.NameParts.FirstOrDefault())) { if (string.IsNullOrEmpty(choiceParts.Key)) { // Choice with no name. Ignore continue; } string? displayName = choiceParts.SingleOrDefault(p => p.NameParts.Skip(1).FirstOrDefault() == "displayName").LocalizedString; string? description = choiceParts.SingleOrDefault(p => p.NameParts.Skip(1).FirstOrDefault() == "description").LocalizedString; results.Add(choiceParts.Key, new ParameterChoiceLocalizationModel(displayName, description)); } return results; } /// <summary> /// Generates post action localization models from the given localized strings. /// </summary> private static IReadOnlyDictionary<string, PostActionLocalizationModel> LoadPostActionModels(List<(string Key, string Value)> localizedStrings) { var results = new Dictionary<string, PostActionLocalizationModel>(); // Property names are in format: postActions/actionId/manualInstructions/instructionId/description // Split them using '/' and store together with the localized string. IEnumerable<(IEnumerable<string> NameParts, string LocalizedString)> strings = localizedStrings .Where(s => s.Key.StartsWith("postActions" + KeySeparator)) .Select(s => (s.Key.Split(KeySeparator).AsEnumerable().Skip(1), s.Value)); foreach (var postActionParts in strings.GroupBy(p => p.NameParts.FirstOrDefault())) { if (string.IsNullOrEmpty(postActionParts.Key)) { // Post action with no ID. Ignore. continue; } string postActionId = postActionParts.Key; string? description = postActionParts.SingleOrDefault(p => p.NameParts.Skip(1).FirstOrDefault() == "description").LocalizedString; var instructions = LoadManualInstructionModels(postActionParts .Where(s => s.NameParts.Skip(1).FirstOrDefault()?.StartsWith("manualInstructions") == true) .Select(s => (s.NameParts.Skip(2), s.LocalizedString))); results[postActionId] = new PostActionLocalizationModel() { Description = description, Instructions = instructions, }; } return results; } /// <summary> /// Generates manual instruction localization models. /// The given parts should begin with the manual instruction id. /// <list type="table"> /// <item>instructionToRestore/text</item> /// <item>instructionToRestart/text</item> /// </list> /// </summary> /// <returns>The localized manual instructions where each key represents the instruction id.</returns> private static IReadOnlyDictionary<string, string> LoadManualInstructionModels(IEnumerable<(IEnumerable<string> NameParts, string LocalizedString)> strings) { var results = new Dictionary<string, string>(); foreach (var instructionParts in strings.GroupBy(p => p.NameParts.FirstOrDefault())) { if (string.IsNullOrEmpty(instructionParts.Key)) { // Instruction with no ID. Ignore. continue; } string id = instructionParts.Key; string? text = instructionParts.SingleOrDefault(p => p.NameParts.Skip(1).FirstOrDefault() == "text").LocalizedString; results[id] = text; } return results; } } }