diff --git a/Nodejs/Product/Nodejs/Nodejs.csproj b/Nodejs/Product/Nodejs/Nodejs.csproj index 28de1a8d6..1009639fd 100644 --- a/Nodejs/Product/Nodejs/Nodejs.csproj +++ b/Nodejs/Product/Nodejs/Nodejs.csproj @@ -312,6 +312,14 @@ PreserveNewest true + + PreserveNewest + true + + + PreserveNewest + true + diff --git a/Nodejs/Product/Nodejs/NodejsPackage.cs b/Nodejs/Product/Nodejs/NodejsPackage.cs index bb90e598e..74480b9cc 100644 --- a/Nodejs/Product/Nodejs/NodejsPackage.cs +++ b/Nodejs/Product/Nodejs/NodejsPackage.cs @@ -24,6 +24,36 @@ namespace Microsoft.NodejsTools { + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] + internal sealed class ProvideUnifiedSettingsOptionPageAttribute : RegistrationAttribute + { + private readonly string categoryName; + private readonly string pageName; + + public ProvideUnifiedSettingsOptionPageAttribute(string categoryName, string pageName) + { + this.categoryName = categoryName; + this.pageName = pageName; + } + + public override void Register(RegistrationContext context) + { + using (var pageKey = context.CreateKey( + string.Format( + CultureInfo.InvariantCulture, + @"ToolsOptionsPages\{0}\{1}", + this.categoryName, + this.pageName))) + { + pageKey.SetValue("IsInUnifiedSettings", 1); + } + } + + public override void Unregister(RegistrationContext context) + { + } + } + /// /// This is the class that implements the package exposed by this assembly. /// @@ -39,6 +69,7 @@ namespace Microsoft.NodejsTools [PackageRegistration(UseManagedResourcesOnly = true)] [Guid(Guids.NodejsPackageString)] [ProvideOptionPage(typeof(NodejsGeneralOptionsPage), "Node.js Tools", "General", 114, 115, true)] + [ProvideUnifiedSettingsOptionPage("Node.js Tools", "General")] [WebSiteProject("JavaScript", "JavaScript")] [ProvideProjectFactory(typeof(NodejsProjectFactory), null, null, null, null, ".\\NullPath", LanguageVsTemplate = NodejsConstants.Nodejs, SortPriority = 0x17)] // outer flavor, no file extension [ProvideMenuResource("Menus.ctmenu", 1)] // This attribute is needed to let the shell know that this package exposes some menus. diff --git a/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsPage.cs b/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsPage.cs index 87b957996..63ed05ede 100644 --- a/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsPage.cs +++ b/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsPage.cs @@ -14,6 +14,12 @@ public class NodejsGeneralOptionsPage : NodejsDialogPage private const string CheckForLongPathsSetting = "CheckForLongPaths"; private NodejsGeneralOptionsControl _window; + private bool _waitOnAbnormalExit; + private bool _waitOnNormalExit; + private bool _editAndContinue; + private bool _waitOnAbnormalExitModified; + private bool _waitOnNormalExitModified; + private bool _editAndContinueModified; public NodejsGeneralOptionsPage() : base("General") @@ -38,18 +44,42 @@ protected override IWin32Window Window /// True if Node processes should pause for input before exiting /// if they exit abnormally. /// - public bool WaitOnAbnormalExit { get; set; } + public bool WaitOnAbnormalExit + { + get => this._waitOnAbnormalExit; + set + { + this._waitOnAbnormalExit = value; + this._waitOnAbnormalExitModified = true; + } + } /// /// True if Node processes should pause for input before exiting /// if they exit normally. /// - public bool WaitOnNormalExit { get; set; } + public bool WaitOnNormalExit + { + get => this._waitOnNormalExit; + set + { + this._waitOnNormalExit = value; + this._waitOnNormalExitModified = true; + } + } /// /// Indicates whether Edit and Continue feature should be enabled. /// - public bool EditAndContinue { get; set; } + public bool EditAndContinue + { + get => this._editAndContinue; + set + { + this._editAndContinue = value; + this._editAndContinueModified = true; + } + } /// /// Resets settings back to their defaults. This should be followed by @@ -65,12 +95,36 @@ public override void ResetSettings() public override void LoadSettingsFromStorage() { - // Load settings from storage. - this.WaitOnAbnormalExit = LoadBool(WaitOnAbnormalExitSetting) ?? true; - this.WaitOnNormalExit = LoadBool(WaitOnNormalExitSetting) ?? false; - this.EditAndContinue = LoadBool(EditAndContinueSetting) ?? true; + this._waitOnAbnormalExit = LoadBool(WaitOnAbnormalExitSetting) ?? true; + this._waitOnNormalExit = LoadBool(WaitOnNormalExitSetting) ?? false; + this._editAndContinue = LoadBool(EditAndContinueSetting) ?? true; + this._waitOnAbnormalExitModified = false; + this._waitOnNormalExitModified = false; + this._editAndContinueModified = false; + + if (this._window != null) + { + this._window.SyncControlWithPageSettings(this); + } + } + + internal void RefreshSettingsFromStorage() + { + if (!this._waitOnAbnormalExitModified) + { + this._waitOnAbnormalExit = LoadBool(WaitOnAbnormalExitSetting) ?? true; + } + + if (!this._waitOnNormalExitModified) + { + this._waitOnNormalExit = LoadBool(WaitOnNormalExitSetting) ?? false; + } + + if (!this._editAndContinueModified) + { + this._editAndContinue = LoadBool(EditAndContinueSetting) ?? true; + } - // Synchronize UI with backing properties. if (this._window != null) { this._window.SyncControlWithPageSettings(this); @@ -79,16 +133,17 @@ public override void LoadSettingsFromStorage() public override void SaveSettingsToStorage() { - // Synchronize backing properties with UI. if (this._window != null) { this._window.SyncPageWithControlSettings(this); } - // Save settings. SaveBool(WaitOnNormalExitSetting, this.WaitOnNormalExit); SaveBool(WaitOnAbnormalExitSetting, this.WaitOnAbnormalExit); SaveBool(EditAndContinueSetting, this.EditAndContinue); + this._waitOnAbnormalExitModified = false; + this._waitOnNormalExitModified = false; + this._editAndContinueModified = false; } } } diff --git a/Nodejs/Product/Nodejs/Project/NodejsProjectLauncher.cs b/Nodejs/Product/Nodejs/Project/NodejsProjectLauncher.cs index fb777c2de..c79375c6d 100644 --- a/Nodejs/Product/Nodejs/Project/NodejsProjectLauncher.cs +++ b/Nodejs/Product/Nodejs/Project/NodejsProjectLauncher.cs @@ -136,10 +136,13 @@ private void StartNodeProcess(string file, string nodePath, bool shouldStartBrow psi.EnvironmentVariables[nameValue.Key] = nameValue.Value; } + var generalOptions = NodejsPackage.Instance.GeneralOptionsPage; + generalOptions.RefreshSettingsFromStorage(); + var process = NodeProcess.Start( psi, - waitOnAbnormal: NodejsPackage.Instance.GeneralOptionsPage.WaitOnAbnormalExit, - waitOnNormal: NodejsPackage.Instance.GeneralOptionsPage.WaitOnNormalExit); + waitOnAbnormal: generalOptions.WaitOnAbnormalExit, + waitOnNormal: generalOptions.WaitOnNormalExit); this._project.OnDispose += process.ResponseToTerminateEvent; diff --git a/Nodejs/Product/Nodejs/UnifiedSettings.pkgdef b/Nodejs/Product/Nodejs/UnifiedSettings.pkgdef new file mode 100644 index 000000000..a8462442f --- /dev/null +++ b/Nodejs/Product/Nodejs/UnifiedSettings.pkgdef @@ -0,0 +1,5 @@ +// CacheTag must change whenever the Unified Settings manifest changes. +[$RootKey$\SettingsManifests\{FE8A8C3D-328A-476D-99F9-2A24B75F8C7F}] +@="Microsoft.NodejsTools.NodejsPackage" +"ManifestPath"="$PackageFolder$\UnifiedSettings\NodejsGeneralOptions.registration.json" +"CacheTag"=qword:01DD3FA6EA131C30 diff --git a/Nodejs/Product/Nodejs/UnifiedSettings/NodejsGeneralOptions.registration.json b/Nodejs/Product/Nodejs/UnifiedSettings/NodejsGeneralOptions.registration.json new file mode 100644 index 000000000..4c1c2a03d --- /dev/null +++ b/Nodejs/Product/Nodejs/UnifiedSettings/NodejsGeneralOptions.registration.json @@ -0,0 +1,95 @@ +{ + "$schema": "https://aka.ms/unified-settings-experience/registration/schema", + "properties": { + "debugging.nodejs.general.waitOnAbnormalExit": { + "type": "boolean", + "title": "@UnifiedSettings_WaitOnAbnormalExit;{FE8A8C3D-328A-476D-99F9-2A24B75F8C7F}", + "default": true, + "order": 0, + "migration": { + "custom": { + "mode": "full", + "inputs": [ + { + "store": "VsUserSettingsRegistry", + "path": "NodejsTools\\Options\\General\\WaitOnAbnormalExit" + } + ], + "map": [ + { + "result": true, + "matches": [ "True" ] + }, + { + "result": false, + "matches": [ "False" ] + } + ] + } + } + }, + "debugging.nodejs.general.waitOnNormalExit": { + "type": "boolean", + "title": "@UnifiedSettings_WaitOnNormalExit;{FE8A8C3D-328A-476D-99F9-2A24B75F8C7F}", + "default": false, + "order": 1, + "migration": { + "custom": { + "mode": "full", + "inputs": [ + { + "store": "VsUserSettingsRegistry", + "path": "NodejsTools\\Options\\General\\WaitOnNormalExit" + } + ], + "map": [ + { + "result": true, + "matches": [ "True" ] + }, + { + "result": false, + "matches": [ "False" ] + } + ] + } + } + }, + "debugging.nodejs.general.editAndContinue": { + "type": "boolean", + "title": "@UnifiedSettings_EditAndContinue;{FE8A8C3D-328A-476D-99F9-2A24B75F8C7F}", + "default": true, + "order": 2, + "migration": { + "custom": { + "mode": "full", + "inputs": [ + { + "store": "VsUserSettingsRegistry", + "path": "NodejsTools\\Options\\General\\EditAndContinue" + } + ], + "map": [ + { + "result": true, + "matches": [ "True" ] + }, + { + "result": false, + "matches": [ "False" ] + } + ] + } + } + } + }, + "categories": { + "debugging.nodejs": { + "title": "@114;{FE8A8C3D-328A-476D-99F9-2A24B75F8C7F}" + }, + "debugging.nodejs.general": { + "title": "@115;{FE8A8C3D-328A-476D-99F9-2A24B75F8C7F}", + "legacyOptionPageId": "EF677A38-0953-39C2-A228-2FBE8F8F082E" + } + } +} diff --git a/Nodejs/Product/Nodejs/VSPackage.resx b/Nodejs/Product/Nodejs/VSPackage.resx index bb986d846..e66cd35a3 100644 --- a/Nodejs/Product/Nodejs/VSPackage.resx +++ b/Nodejs/Product/Nodejs/VSPackage.resx @@ -138,6 +138,15 @@ General + + Wait for input when process exits abnormally + + + Wait for input when process exits normally + + + Enable Edit and Continue + Npm diff --git a/Nodejs/Tests/Core/NodejsGeneralOptionsTests.cs b/Nodejs/Tests/Core/NodejsGeneralOptionsTests.cs new file mode 100644 index 000000000..04057c22c --- /dev/null +++ b/Nodejs/Tests/Core/NodejsGeneralOptionsTests.cs @@ -0,0 +1,366 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Resources; +using Microsoft.NodejsTools.Options; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json.Linq; + +namespace NodejsTests +{ + [TestClass] + public class NodejsGeneralOptionsTests + { + private const string PackageGuid = "FE8A8C3D-328A-476D-99F9-2A24B75F8C7F"; + private const string LegacyPageGuid = "EF677A38-0953-39C2-A228-2FBE8F8F082E"; + + [TestInitialize] + public void InitializeThreadHelper() + { + var contextField = typeof(ThreadHelper).GetField( + "_joinableTaskContextCache", + BindingFlags.NonPublic | BindingFlags.Static); + if (contextField.GetValue(null) == null) + { + _ = System.Windows.Threading.Dispatcher.CurrentDispatcher; + typeof(ThreadHelper) + .GetMethod("SetUIThread", BindingFlags.NonPublic | BindingFlags.Static) + .Invoke(null, null); + contextField.SetValue(null, Activator.CreateInstance(contextField.FieldType)); + } + } + + [TestMethod, Priority(0)] + public void GeneralOptionsUseLegacyDefaults() + { + var page = new TestGeneralOptionsPage(); + + page.LoadSettingsFromStorage(); + + Assert.IsTrue(page.WaitOnAbnormalExit); + Assert.IsFalse(page.WaitOnNormalExit); + Assert.IsTrue(page.EditAndContinue); + } + + [TestMethod, Priority(0)] + public void GeneralOptionsRefreshUnmodifiedValues() + { + var page = new TestGeneralOptionsPage + { + StoredValues = + { + ["WaitOnAbnormalExit"] = false, + ["WaitOnNormalExit"] = true, + ["EditAndContinue"] = false + } + }; + page.LoadSettingsFromStorage(); + + page.StoredValues["WaitOnAbnormalExit"] = true; + page.StoredValues["WaitOnNormalExit"] = false; + page.StoredValues["EditAndContinue"] = true; + page.RefreshSettingsFromStorage(); + + Assert.IsTrue(page.WaitOnAbnormalExit); + Assert.IsFalse(page.WaitOnNormalExit); + Assert.IsTrue(page.EditAndContinue); + } + + [TestMethod, Priority(0)] + public void GeneralOptionsRefreshPreservesUnsavedConsumerValues() + { + var page = new TestGeneralOptionsPage + { + StoredValues = + { + ["WaitOnAbnormalExit"] = true, + ["WaitOnNormalExit"] = true, + ["EditAndContinue"] = false + } + }; + page.LoadSettingsFromStorage(); + + page.WaitOnAbnormalExit = false; + page.StoredValues["WaitOnNormalExit"] = false; + page.StoredValues["EditAndContinue"] = true; + page.RefreshSettingsFromStorage(); + + Assert.IsFalse(page.WaitOnAbnormalExit); + Assert.IsFalse(page.WaitOnNormalExit); + Assert.IsTrue(page.EditAndContinue); + } + + [TestMethod, Priority(0)] + public void GeneralOptionsSaveAllLegacyValues() + { + var page = new TestGeneralOptionsPage + { + WaitOnAbnormalExit = false, + WaitOnNormalExit = true, + EditAndContinue = false + }; + + page.SaveSettingsToStorage(); + + Assert.AreEqual(false, page.SavedValues["WaitOnAbnormalExit"]); + Assert.AreEqual(true, page.SavedValues["WaitOnNormalExit"]); + Assert.AreEqual(false, page.SavedValues["EditAndContinue"]); + } + + [TestMethod, Priority(0)] + public void UnifiedSettingsManifestMatchesLegacyContract() + { + var manifestPath = Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, + "UnifiedSettings", + "NodejsGeneralOptions.registration.json"); + var manifest = JObject.Parse(File.ReadAllText(manifestPath)); + var properties = (JObject)manifest["properties"]; + var categories = (JObject)manifest["categories"]; + + Assert.AreEqual(3, properties.Count); + AssertSetting( + properties, + "debugging.nodejs.general.waitOnAbnormalExit", + "WaitOnAbnormalExit", + true, + "UnifiedSettings_WaitOnAbnormalExit"); + AssertSetting( + properties, + "debugging.nodejs.general.waitOnNormalExit", + "WaitOnNormalExit", + false, + "UnifiedSettings_WaitOnNormalExit"); + AssertSetting( + properties, + "debugging.nodejs.general.editAndContinue", + "EditAndContinue", + true, + "UnifiedSettings_EditAndContinue"); + + Assert.AreEqual(2, categories.Count); + Assert.AreEqual( + string.Format("@114;{{{0}}}", PackageGuid), + (string)categories["debugging.nodejs"]["title"]); + Assert.AreEqual( + string.Format("@115;{{{0}}}", PackageGuid), + (string)categories["debugging.nodejs.general"]["title"]); + Assert.AreEqual( + LegacyPageGuid, + (string)categories["debugging.nodejs.general"]["legacyOptionPageId"]); + } + + [TestMethod, Priority(0)] + public void UnifiedSettingsPackageRegistrationPointsToManifest() + { + var pkgdef = File.ReadAllText( + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UnifiedSettings.pkgdef")); + + StringAssert.Contains(pkgdef, @"SettingsManifests\{" + PackageGuid + "}"); + StringAssert.Contains( + pkgdef, + @"""ManifestPath""=""$PackageFolder$\UnifiedSettings\NodejsGeneralOptions.registration.json"""); + StringAssert.Contains(pkgdef, @"""CacheTag""=qword:"); + } + + [TestMethod, Priority(0)] + public void UnifiedSettingsHierarchyExcludesLegacyPlaceholder() + { + Assert.AreEqual(new Guid(LegacyPageGuid), typeof(NodejsGeneralOptionsPage).GUID); + + var generatedPkgdef = File.ReadAllText( + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Microsoft.NodejsTools.pkgdef")); + var pageKey = @"[$RootKey$\ToolsOptionsPages\Node.js Tools\General]"; + var pageRegistration = GetPkgdefRegistration( + generatedPkgdef, + pageKey).ToUpperInvariant(); + + Assert.IsFalse(string.IsNullOrEmpty(pageRegistration)); + StringAssert.Contains( + pageRegistration, + string.Format(@"""PAGE""=""{{{0}}}""", LegacyPageGuid)); + StringAssert.Contains( + pageRegistration, + @"""ISINUNIFIEDSETTINGS""=DWORD:00000001"); + var isInUnifiedSettings = pageRegistration.Contains( + @"""ISINUNIFIEDSETTINGS""=DWORD:00000001"); + + var manifestPath = Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, + "UnifiedSettings", + "NodejsGeneralOptions.registration.json"); + var categories = (JObject)JObject.Parse( + File.ReadAllText(manifestPath))["categories"]; + var realCategoryCount = 0; + foreach (var categoryProperty in categories.Properties()) + { + var category = (JObject)categoryProperty.Value; + if (string.Equals( + LegacyPageGuid, + (string)category["legacyOptionPageId"], + StringComparison.OrdinalIgnoreCase)) + { + ++realCategoryCount; + } + } + + // ToolsOptionsHierarchyMerger excludes legacy leaves carrying this registration value. + var legacyPlaceholderCount = isInUnifiedSettings ? 0 : 1; + Assert.AreEqual(1, realCategoryCount); + Assert.AreEqual(0, legacyPlaceholderCount); + } + + private static string GetPkgdefRegistration(string pkgdef, string key) + { + var registration = string.Empty; + var searchStart = 0; + + while (searchStart < pkgdef.Length) + { + var keyStart = pkgdef.IndexOf(key, searchStart, StringComparison.Ordinal); + if (keyStart < 0) + { + break; + } + + var keyEnd = pkgdef.IndexOf( + "\n[", + keyStart + key.Length, + StringComparison.Ordinal); + if (keyEnd < 0) + { + keyEnd = pkgdef.Length; + } + + registration += pkgdef.Substring(keyStart, keyEnd - keyStart); + searchStart = keyEnd; + } + + return registration; + } + + [TestMethod, Priority(0)] + public void UnifiedSettingsResourcesResolveWithPackageProviderSyntax() + { + var manifestPath = Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, + "UnifiedSettings", + "NodejsGeneralOptions.registration.json"); + var manifest = JObject.Parse(File.ReadAllText(manifestPath)); + var resourceTokens = new List(); + + AddDisplayResourceTokens((JObject)manifest["properties"], resourceTokens); + AddDisplayResourceTokens((JObject)manifest["categories"], resourceTokens); + + Assert.AreEqual(5, resourceTokens.Count); + + var resourceManager = new ResourceManager( + "VSPackage", + typeof(NodejsGeneralOptionsPage).Assembly); + + foreach (var token in resourceTokens) + { + var fallbackSeparator = token.IndexOf('|'); + var resourceId = fallbackSeparator < 0 + ? token + : token.Substring(0, fallbackSeparator); + var providerSeparator = resourceId.IndexOf(';'); + + Assert.AreEqual('@', resourceId[0], token); + Assert.IsTrue(providerSeparator > 1, token); + + var resourceName = resourceId.Substring(1, providerSeparator - 1); + Assert.IsTrue( + Guid.TryParse(resourceId.Substring(providerSeparator + 1), out var packageGuid), + token); + Assert.AreEqual(new Guid(PackageGuid), packageGuid, token); + + var resolved = resourceManager.GetString( + resourceName, + CultureInfo.InvariantCulture); + Assert.IsFalse(string.IsNullOrEmpty(resolved), token); + Assert.AreNotEqual(token, resolved, token); + } + } + + private static void AddDisplayResourceTokens( + JObject definitions, + ICollection resourceTokens) + { + foreach (var definitionProperty in definitions.Properties()) + { + var definition = (JObject)definitionProperty.Value; + foreach (var fieldName in new[] { "title", "description" }) + { + var token = (string)definition[fieldName]; + if (token != null) + { + resourceTokens.Add(token); + } + } + } + } + + private static void AssertSetting( + JObject properties, + string moniker, + string legacyName, + bool defaultValue, + string resourceName) + { + var property = (JObject)properties[moniker]; + Assert.IsNotNull(property, moniker); + Assert.AreEqual("boolean", (string)property["type"], moniker); + Assert.AreEqual(defaultValue, (bool)property["default"], moniker); + Assert.AreEqual( + string.Format("@{0};{{{1}}}", resourceName, PackageGuid), + (string)property["title"], + moniker); + + var migration = (JObject)property["migration"]["custom"]; + Assert.AreEqual("full", (string)migration["mode"], moniker); + Assert.AreEqual(1, ((JArray)migration["inputs"]).Count, moniker); + Assert.AreEqual( + "VsUserSettingsRegistry", + (string)migration["inputs"][0]["store"], + moniker); + Assert.AreEqual( + @"NodejsTools\Options\General\" + legacyName, + (string)migration["inputs"][0]["path"], + moniker); + + var map = (JArray)migration["map"]; + Assert.AreEqual(2, map.Count, moniker); + Assert.AreEqual(true, (bool)map[0]["result"], moniker); + Assert.AreEqual("True", (string)map[0]["matches"][0], moniker); + Assert.AreEqual(false, (bool)map[1]["result"], moniker); + Assert.AreEqual("False", (string)map[1]["matches"][0], moniker); + } + + private sealed class TestGeneralOptionsPage : NodejsGeneralOptionsPage + { + internal IDictionary StoredValues { get; } = + new Dictionary(); + + internal IDictionary SavedValues { get; } = + new Dictionary(); + + internal override bool? LoadBool(string name) + { + return this.StoredValues.TryGetValue(name, out var value) + ? value + : (bool?)null; + } + + internal override void SaveBool(string name, bool value) + { + this.SavedValues[name] = value; + } + } + } +} diff --git a/Nodejs/Tests/Core/NodejsTests.csproj b/Nodejs/Tests/Core/NodejsTests.csproj index 062cd1c7d..50c65488f 100644 --- a/Nodejs/Tests/Core/NodejsTests.csproj +++ b/Nodejs/Tests/Core/NodejsTests.csproj @@ -83,6 +83,7 @@ + @@ -157,6 +158,16 @@ + + + UnifiedSettings.pkgdef + PreserveNewest + + + UnifiedSettings\NodejsGeneralOptions.registration.json + PreserveNewest + + ResXFileCodeGenerator diff --git a/loc/lcl/CHS/Microsoft.NodejsTools.dll.lcl b/loc/lcl/CHS/Microsoft.NodejsTools.dll.lcl index 14e89e37f..f1e317fb4 100644 --- a/loc/lcl/CHS/Microsoft.NodejsTools.dll.lcl +++ b/loc/lcl/CHS/Microsoft.NodejsTools.dll.lcl @@ -4447,6 +4447,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/CHT/Microsoft.NodejsTools.dll.lcl b/loc/lcl/CHT/Microsoft.NodejsTools.dll.lcl index cc5b333e5..0e681b680 100644 --- a/loc/lcl/CHT/Microsoft.NodejsTools.dll.lcl +++ b/loc/lcl/CHT/Microsoft.NodejsTools.dll.lcl @@ -4447,6 +4447,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/CSY/Microsoft.NodejsTools.dll.lcl b/loc/lcl/CSY/Microsoft.NodejsTools.dll.lcl index c280bc767..f54f2a286 100644 --- a/loc/lcl/CSY/Microsoft.NodejsTools.dll.lcl +++ b/loc/lcl/CSY/Microsoft.NodejsTools.dll.lcl @@ -4447,6 +4447,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/DEU/Microsoft.NodejsTools.dll.lcl b/loc/lcl/DEU/Microsoft.NodejsTools.dll.lcl index fea5811e8..2faed91e4 100644 --- a/loc/lcl/DEU/Microsoft.NodejsTools.dll.lcl +++ b/loc/lcl/DEU/Microsoft.NodejsTools.dll.lcl @@ -4447,6 +4447,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/ESN/Microsoft.NodejsTools.dll.lcl b/loc/lcl/ESN/Microsoft.NodejsTools.dll.lcl index 019f58b42..acc90588f 100644 --- a/loc/lcl/ESN/Microsoft.NodejsTools.dll.lcl +++ b/loc/lcl/ESN/Microsoft.NodejsTools.dll.lcl @@ -4447,6 +4447,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/FRA/Microsoft.NodejsTools.dll.lcl b/loc/lcl/FRA/Microsoft.NodejsTools.dll.lcl index caaa52bb0..f184063cb 100644 --- a/loc/lcl/FRA/Microsoft.NodejsTools.dll.lcl +++ b/loc/lcl/FRA/Microsoft.NodejsTools.dll.lcl @@ -4447,6 +4447,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/ITA/Microsoft.NodejsTools.dll.lcl b/loc/lcl/ITA/Microsoft.NodejsTools.dll.lcl index 5de3f68c4..353539331 100644 --- a/loc/lcl/ITA/Microsoft.NodejsTools.dll.lcl +++ b/loc/lcl/ITA/Microsoft.NodejsTools.dll.lcl @@ -4447,6 +4447,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/JPN/Microsoft.NodejsTools.dll.lcl b/loc/lcl/JPN/Microsoft.NodejsTools.dll.lcl index 6c38f370a..b666d7e38 100644 --- a/loc/lcl/JPN/Microsoft.NodejsTools.dll.lcl +++ b/loc/lcl/JPN/Microsoft.NodejsTools.dll.lcl @@ -4447,6 +4447,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/KOR/Microsoft.NodejsTools.dll.lcl b/loc/lcl/KOR/Microsoft.NodejsTools.dll.lcl index e9730c6d9..646b4d2b5 100644 --- a/loc/lcl/KOR/Microsoft.NodejsTools.dll.lcl +++ b/loc/lcl/KOR/Microsoft.NodejsTools.dll.lcl @@ -4447,6 +4447,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/PLK/Microsoft.NodejsTools.dll.lcl b/loc/lcl/PLK/Microsoft.NodejsTools.dll.lcl index 6b4ede4f3..93c9e74d5 100644 --- a/loc/lcl/PLK/Microsoft.NodejsTools.dll.lcl +++ b/loc/lcl/PLK/Microsoft.NodejsTools.dll.lcl @@ -4447,6 +4447,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/PTB/Microsoft.NodejsTools.dll.lcl b/loc/lcl/PTB/Microsoft.NodejsTools.dll.lcl index 39a9394f7..5e560d724 100644 --- a/loc/lcl/PTB/Microsoft.NodejsTools.dll.lcl +++ b/loc/lcl/PTB/Microsoft.NodejsTools.dll.lcl @@ -4447,6 +4447,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/RUS/Microsoft.NodejsTools.dll.lcl b/loc/lcl/RUS/Microsoft.NodejsTools.dll.lcl index fe2fdc9bc..0f6e43106 100644 --- a/loc/lcl/RUS/Microsoft.NodejsTools.dll.lcl +++ b/loc/lcl/RUS/Microsoft.NodejsTools.dll.lcl @@ -4447,6 +4447,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/TRK/Microsoft.NodejsTools.dll.lcl b/loc/lcl/TRK/Microsoft.NodejsTools.dll.lcl index 23537d10f..e82af7894 100644 --- a/loc/lcl/TRK/Microsoft.NodejsTools.dll.lcl +++ b/loc/lcl/TRK/Microsoft.NodejsTools.dll.lcl @@ -4447,6 +4447,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +