| File: UILanguageOverride.cs | Web Access |
| Project: src\sdk\src\Cli\Microsoft.DotNet.Cli.Utils\Microsoft.DotNet.Cli.Utils.csproj (Microsoft.DotNet.Cli.Utils) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Globalization; using System.Security; using Microsoft.Win32; namespace Microsoft.DotNet.Cli.Utils; internal static class UILanguageOverride { internal const string DOTNET_CLI_UI_LANGUAGE = nameof(DOTNET_CLI_UI_LANGUAGE); private const string DOTNET_CLI_CONSOLE_USE_DEFAULT_ENCODING = nameof(DOTNET_CLI_CONSOLE_USE_DEFAULT_ENCODING); private const string VSLANG = nameof(VSLANG); private const string PreferredUILang = nameof(PreferredUILang); // We choose UTF8 as the default encoding as opposed to specific language encodings because it supports emojis & other chars in .NET. private static readonly Encoding s_defaultMultilingualEncoding = Encoding.UTF8; public static void Setup() { CultureInfo? language = GetOverriddenUILanguage(); if (language != null) { ApplyOverrideToCurrentProcess(language); FlowOverrideToChildProcesses(language); } if (Env.GetEnvironmentVariable(DOTNET_CLI_CONSOLE_USE_DEFAULT_ENCODING) != "1") { if ( !CultureInfo.CurrentUICulture.TwoLetterISOLanguageName.Equals("en", StringComparison.InvariantCultureIgnoreCase) && #if NET OperatingSystemSupportsUtf8() #else CurrentPlatformIsWindowsAndOfficiallySupportsUTF8Encoding() #endif ) { // Setting both encodings causes a change in the CHCP, making it so we don't need to P-Invoke ourselves. Console.OutputEncoding = s_defaultMultilingualEncoding; Console.InputEncoding = s_defaultMultilingualEncoding; // If the InputEncoding is not set, the encoding will work in CMD but not in Powershell, as the raw CHCP page won't be changed. } } } #if NET public static bool OperatingSystemSupportsUtf8() { return !OperatingSystem.IsIOS() && !OperatingSystem.IsAndroid() && !OperatingSystem.IsTvOS() && !OperatingSystem.IsBrowser() && (!OperatingSystem.IsWindows() || OperatingSystem.IsWindowsVersionAtLeast(10, 0, 18363)); } #endif private static void ApplyOverrideToCurrentProcess(CultureInfo language) { CultureInfo.DefaultThreadCurrentUICulture = language; // We don't need to change CurrentUICulture, as it will be changed by DefaultThreadCurrentUICulture on NET Core (but not Framework) apps. } 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 SetIfNotAlreadySet(PreferredUILang, language.Name); // for C#/VB targets that pass $(PreferredUILang) to compiler } /// <summary> /// Look first at UI Language Overrides. (DOTNET_CLI_UI_LANGUAGE and VSLANG). Does NOT check System Locale or OS Display Language. /// </summary> /// <returns>The custom language that was set by the user. /// DOTNET_CLI_UI_LANGUAGE > VSLANG. Returns null if none are set.</returns> public static CultureInfo? GetOverriddenUILanguage() { // DOTNET_CLI_UI_LANGUAGE=<culture name> 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=<lcid> 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()); } private static bool CurrentPlatformIsWindowsAndOfficiallySupportsUTF8Encoding() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version.Major >= 10) // UTF-8 is only officially supported on 10+. { return CurrentPlatformOfficiallySupportsUTF8Encoding(); } return false; } private static bool CurrentPlatformOfficiallySupportsUTF8Encoding() { Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); try { using RegistryKey? windowsVersionRegistry = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"); var buildNumber = windowsVersionRegistry?.GetValue("CurrentBuildNumber")?.ToString() ?? string.Empty; const int buildNumberThatOfficiallySupportsUTF8 = 18363; return int.Parse(buildNumber) >= buildNumberThatOfficiallySupportsUTF8 || ForceUniversalEncodingOptInEnabled(); } catch (Exception ex) when (ex is SecurityException || ex is ObjectDisposedException) { // We don't want to break those in VS on older versions of Windows with a non-en language. // Allow those without registry permissions to force the encoding, however. return ForceUniversalEncodingOptInEnabled(); } } private static bool ForceUniversalEncodingOptInEnabled() { return string.Equals(Environment.GetEnvironmentVariable("DOTNET_CLI_FORCE_UTF8_ENCODING"), "true", StringComparison.OrdinalIgnoreCase); } }