From 60e26371436a595f71e574d4814027cef7f7590e Mon Sep 17 00:00:00 2001 From: Nick Guerrera Date: Wed, 28 Jun 2017 18:09:12 -0700 Subject: [PATCH 1/4] Allow CLI UI language to be overridden by an environment variable --- src/dotnet/Program.cs | 3 ++ src/dotnet/UILanguageOverride.cs | 76 ++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/dotnet/UILanguageOverride.cs diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs index 51704ece6..08a711f93 100644 --- a/src/dotnet/Program.cs +++ b/src/dotnet/Program.cs @@ -198,6 +198,9 @@ namespace Microsoft.DotNet.Cli // by default, .NET Core doesn't have all code pages needed for Console apps. // see the .NET Core Notes in https://msdn.microsoft.com/en-us/library/system.diagnostics.process(v=vs.110).aspx Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + // Honor UI language customization + UILanguageOverride.Setup(); } internal static bool TryGetBuiltInCommand(string commandName, out BuiltInCommandMetadata builtInCommand) diff --git a/src/dotnet/UILanguageOverride.cs b/src/dotnet/UILanguageOverride.cs new file mode 100644 index 000000000..00282389b --- /dev/null +++ b/src/dotnet/UILanguageOverride.cs @@ -0,0 +1,76 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Globalization; + +namespace Microsoft.DotNet.Cli +{ + internal static class UILanguageOverride + { + private const string DOTNET_CLI_UI_LANGUAGE = nameof(DOTNET_CLI_UI_LANGUAGE); + private const string VSLANG = nameof(VSLANG); + private const string PreferredUILang = nameof(PreferredUILang); + + public static void Setup() + { + CultureInfo language = GetOverriddenUILanguage(); + if (language == null) + { + return; + } + + // Make the current process respect the override. + CultureInfo.DefaultThreadCurrentUICulture = language; + + // Pass down the override to other processes that we start via appropriate environment variables + // Do not override any environment variables that are already set as we do not want to clobber a more granular setting with our global setting. + SetIfNotAlreadySet(DOTNET_CLI_UI_LANGUAGE, language.Name); + SetIfNotAlreadySet(VSLANG, language.LCID); // for tools following VS guidelines to just work in CLI + SetIfNotAlreadySet(PreferredUILang, language.Name); // for C#/VB targets that pass $(PreferredUILang) to compiler + } + + private static CultureInfo GetOverriddenUILanguage() + { + // DOTNET_CLI_UI_LANGUAGE= is the main way for users to customize the CLI's UI language. + string dotnetCliLanguage = Environment.GetEnvironmentVariable(DOTNET_CLI_UI_LANGUAGE); + if (dotnetCliLanguage != null) + { + try + { + return new CultureInfo(dotnetCliLanguage); + } + catch (CultureNotFoundException) { } + } + + // VSLANG= is set by VS and we respect that as well so that we will respect the VS + // language preference if we're invoked by VS. + string vsLang = Environment.GetEnvironmentVariable(VSLANG); + if (vsLang != null && int.TryParse(VSLANG, out int vsLcid)) + { + try + { + return new CultureInfo(vsLcid); + } + catch (ArgumentOutOfRangeException) { } + catch (CultureNotFoundException) { } + } + + return null; + } + + private static void SetIfNotAlreadySet(string environmentVariableName, string value) + { + string currentValue = Environment.GetEnvironmentVariable(environmentVariableName); + if (currentValue == null) + { + Environment.SetEnvironmentVariable(environmentVariableName, value); + } + } + + private static void SetIfNotAlreadySet(string environmentVariableName, int value) + { + SetIfNotAlreadySet(environmentVariableName, value.ToString()); + } + } +} From a1c423c0f64c49cf5b1050bc34a31a49baaa63ee Mon Sep 17 00:00:00 2001 From: Nick Guerrera Date: Tue, 18 Jul 2017 08:34:08 -0700 Subject: [PATCH 2/4] Honor UI language override in test runs Also fix some incorrect unlocalized test expectations that slipped in. --- .../dotnet-cli-build/EnvironmentVariableFilter.cs | 1 + src/Microsoft.DotNet.Cli.Utils/Product.cs | 2 +- .../TestBase.cs | 10 ++++++++++ test/dotnet-new.Tests/NewCommandTests.cs | 12 +++++++++--- test/dotnet-run.Tests/GivenDotnetRunRunsCsProj.cs | 12 +++++++----- .../ParserTests/ValdidationMessageTests.cs | 2 -- 6 files changed, 28 insertions(+), 11 deletions(-) diff --git a/build_projects/dotnet-cli-build/EnvironmentVariableFilter.cs b/build_projects/dotnet-cli-build/EnvironmentVariableFilter.cs index 432f30386..6071d0765 100644 --- a/build_projects/dotnet-cli-build/EnvironmentVariableFilter.cs +++ b/build_projects/dotnet-cli-build/EnvironmentVariableFilter.cs @@ -28,6 +28,7 @@ namespace Microsoft.DotNet.Cli.Build private IEnumerable _environmentVariablesToKeep = new string [] { "DOTNET_CLI_TELEMETRY_SESSIONID", + "DOTNET_CLI_UI_LANGUAGE", "DOTNET_MULTILEVEL_LOOKUP", "DOTNET_RUNTIME_ID", "DOTNET_SKIP_FIRST_TIME_EXPERIENCE", diff --git a/src/Microsoft.DotNet.Cli.Utils/Product.cs b/src/Microsoft.DotNet.Cli.Utils/Product.cs index 023004ce4..8811847b3 100644 --- a/src/Microsoft.DotNet.Cli.Utils/Product.cs +++ b/src/Microsoft.DotNet.Cli.Utils/Product.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Cli.Utils { public class Product { - public static readonly string LongName = LocalizableStrings.DotNetCommandLineTools; + public static string LongName => LocalizableStrings.DotNetCommandLineTools; public static readonly string Version = GetProductVersion(); private static string GetProductVersion() diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs index e2e1269a6..b377b4331 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -23,6 +24,15 @@ namespace Microsoft.DotNet.Tools.Test.Utilities private TempRoot _temp; private static TestAssets s_testAssets; + static TestBase() + { + // set culture of test process to match CLI sub-processes when the UI language is overriden. + string overriddenUILanguage = Environment.GetEnvironmentVariable("DOTNET_CLI_UI_LANGUAGE"); + if (overriddenUILanguage != null) + { + CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo(overriddenUILanguage); + } + } protected static string RepoRoot { diff --git a/test/dotnet-new.Tests/NewCommandTests.cs b/test/dotnet-new.Tests/NewCommandTests.cs index 45eccf5c7..e22cb821c 100644 --- a/test/dotnet-new.Tests/NewCommandTests.cs +++ b/test/dotnet-new.Tests/NewCommandTests.cs @@ -7,7 +7,7 @@ using Xunit; namespace Microsoft.DotNet.New.Tests { - public class NewCommandTests + public class NewCommandTests : TestBase { [Fact] public void WhenSwitchIsSkippedThenItPrintsError() @@ -16,7 +16,10 @@ namespace Microsoft.DotNet.New.Tests cmd.ExitCode.Should().NotBe(0); - cmd.StdErr.Should().StartWith("No templates matched the input template name: Web1.1."); + if (!DotnetUnderTest.IsLocalized()) + { + cmd.StdErr.Should().StartWith("No templates matched the input template name: Web1.1."); + } } [Fact] @@ -26,7 +29,10 @@ namespace Microsoft.DotNet.New.Tests cmd.ExitCode.Should().NotBe(0); - cmd.StdErr.Should().StartWith("Unable to determine the desired template from the input template name: c."); + if (!DotnetUnderTest.IsLocalized()) + { + cmd.StdErr.Should().StartWith("Unable to determine the desired template from the input template name: c."); + } } } } diff --git a/test/dotnet-run.Tests/GivenDotnetRunRunsCsProj.cs b/test/dotnet-run.Tests/GivenDotnetRunRunsCsProj.cs index 36a0d3fde..5fa67328e 100644 --- a/test/dotnet-run.Tests/GivenDotnetRunRunsCsProj.cs +++ b/test/dotnet-run.Tests/GivenDotnetRunRunsCsProj.cs @@ -7,6 +7,8 @@ using Microsoft.DotNet.TestFramework; using Microsoft.DotNet.Tools.Test.Utilities; using Xunit; +using LocalizableStrings = Microsoft.DotNet.Tools.Run.LocalizableStrings; + namespace Microsoft.DotNet.Cli.Run.Tests { public class GivenDotnetRunBuildsCsproj : TestBase @@ -280,7 +282,7 @@ namespace Microsoft.DotNet.Cli.Run.Tests .ExecuteWithCapturedOutput("--launch-profile test") .Should().Pass() .And.HaveStdOutContaining("Hello World!") - .And.HaveStdErrContaining("The specified launch profile could not be located."); + .And.HaveStdErrContaining(LocalizableStrings.RunCommandExceptionCouldNotLocateALaunchSettingsFile); } [Fact] @@ -368,7 +370,7 @@ namespace Microsoft.DotNet.Cli.Run.Tests .ExecuteWithCapturedOutput("--launch-profile Third") .Should().Pass() .And.HaveStdOutContaining("(NO MESSAGE)") - .And.HaveStdErrContaining("The launch profile \"Third\" could not be applied."); + .And.HaveStdErrContaining(string.Format(LocalizableStrings.RunCommandExceptionCouldNotApplyLaunchSettings, "Third", "").Trim()); } [Fact] @@ -396,7 +398,7 @@ namespace Microsoft.DotNet.Cli.Run.Tests .ExecuteWithCapturedOutput("--launch-profile \"IIS Express\"") .Should().Pass() .And.HaveStdOutContaining("(NO MESSAGE)") - .And.HaveStdErrContaining("The launch profile \"IIS Express\" could not be applied."); + .And.HaveStdErrContaining(string.Format(LocalizableStrings.RunCommandExceptionCouldNotApplyLaunchSettings, "IIS Express", "").Trim()); } [Fact] @@ -485,7 +487,7 @@ namespace Microsoft.DotNet.Cli.Run.Tests cmd.Should().Pass() .And.HaveStdOutContaining("(NO MESSAGE)") - .And.HaveStdErrContaining("The launch profile \"(Default)\" could not be applied."); + .And.HaveStdErrContaining(string.Format(LocalizableStrings.RunCommandExceptionCouldNotApplyLaunchSettings, LocalizableStrings.DefaultLaunchProfileDisplayName, "").Trim()); } [Fact] @@ -514,7 +516,7 @@ namespace Microsoft.DotNet.Cli.Run.Tests cmd.Should().Pass() .And.HaveStdOutContaining("(NO MESSAGE)") - .And.HaveStdErrContaining("The launch profile \"(Default)\" could not be applied."); + .And.HaveStdErrContaining(string.Format(LocalizableStrings.RunCommandExceptionCouldNotApplyLaunchSettings, LocalizableStrings.DefaultLaunchProfileDisplayName, "").Trim()); } } } diff --git a/test/dotnet.Tests/ParserTests/ValdidationMessageTests.cs b/test/dotnet.Tests/ParserTests/ValdidationMessageTests.cs index 60b65fb29..41d184d6a 100644 --- a/test/dotnet.Tests/ParserTests/ValdidationMessageTests.cs +++ b/test/dotnet.Tests/ParserTests/ValdidationMessageTests.cs @@ -11,8 +11,6 @@ namespace Microsoft.DotNet.Tests.ParserTests { public class ValidationMessageTests { - private readonly ITestOutputHelper output; - [Fact] public void ValidationMessagesFormatCorrectly() { From 85573db000f82a3510010f8ced945083e74b5606 Mon Sep 17 00:00:00 2001 From: Nick Guerrera Date: Wed, 19 Jul 2017 10:25:45 -0700 Subject: [PATCH 3/4] Respond to PR feedback: use method names not comments --- src/dotnet/Program.cs | 1 - src/dotnet/UILanguageOverride.cs | 13 +++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs index 08a711f93..4611ec7b7 100644 --- a/src/dotnet/Program.cs +++ b/src/dotnet/Program.cs @@ -199,7 +199,6 @@ namespace Microsoft.DotNet.Cli // see the .NET Core Notes in https://msdn.microsoft.com/en-us/library/system.diagnostics.process(v=vs.110).aspx Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - // Honor UI language customization UILanguageOverride.Setup(); } diff --git a/src/dotnet/UILanguageOverride.cs b/src/dotnet/UILanguageOverride.cs index 00282389b..b93547b74 100644 --- a/src/dotnet/UILanguageOverride.cs +++ b/src/dotnet/UILanguageOverride.cs @@ -15,15 +15,20 @@ namespace Microsoft.DotNet.Cli public static void Setup() { CultureInfo language = GetOverriddenUILanguage(); - if (language == null) + if (language != null) { - return; + ApplyOverrideToCurrentProcess(language); + FlowOverrideToChildProcesses(language); } + } - // Make the current process respect the override. + private static void ApplyOverrideToCurrentProcess(CultureInfo language) + { CultureInfo.DefaultThreadCurrentUICulture = language; + } - // Pass down the override to other processes that we start via appropriate environment variables + private static void FlowOverrideToChildProcesses(CultureInfo language) + { // Do not override any environment variables that are already set as we do not want to clobber a more granular setting with our global setting. SetIfNotAlreadySet(DOTNET_CLI_UI_LANGUAGE, language.Name); SetIfNotAlreadySet(VSLANG, language.LCID); // for tools following VS guidelines to just work in CLI From 368531715e2b1a8bf7abac3e9bc5047a8bd709f4 Mon Sep 17 00:00:00 2001 From: Nick Guerrera Date: Wed, 19 Jul 2017 11:51:38 -0700 Subject: [PATCH 4/4] Fix VSLANG handling typo --- src/dotnet/UILanguageOverride.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotnet/UILanguageOverride.cs b/src/dotnet/UILanguageOverride.cs index b93547b74..5e44e56cc 100644 --- a/src/dotnet/UILanguageOverride.cs +++ b/src/dotnet/UILanguageOverride.cs @@ -51,7 +51,7 @@ namespace Microsoft.DotNet.Cli // VSLANG= is set by VS and we respect that as well so that we will respect the VS // language preference if we're invoked by VS. string vsLang = Environment.GetEnvironmentVariable(VSLANG); - if (vsLang != null && int.TryParse(VSLANG, out int vsLcid)) + if (vsLang != null && int.TryParse(vsLang, out int vsLcid)) { try {