Skip to content
Open
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
8 changes: 7 additions & 1 deletion doc/ReleaseNotes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
## New in v1.29

Nothing yet.
## New Features

### Output locale override

Added a persistent `output.locale` setting to override winget interface language using a BCP47 tag.

Usage: add `"output": { "locale": "de-DE" }` to `settings.json`.

## Bug Fixes

Expand Down
17 changes: 17 additions & 0 deletions doc/Settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,23 @@ The `interactivity` settings control whether winget may show interactive prompts

If set to true, the `interactivity.disable` setting will prevent any interactive prompt from being shown.

## Output

The `output` settings control how winget presents CLI output.

### locale

The `locale` setting overrides winget interface language by BCP47 tag (for example, `en-US`). If this setting is missing or invalid, winget uses the default Windows globalization behavior.

> [!NOTE]
> This only affects winget interface strings. It does not change package metadata localization or installer selection behavior.

```json
"output": {
"locale": "en-US"
},
```

## Experimental Features

To allow work to be done and distributed to early adopters for feedback, settings can be used to enable "experimental" features.
Expand Down
18 changes: 17 additions & 1 deletion doc/windows/package-manager/winget/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,23 @@ The `locale` behavior affects the choice of installer based on installer locale.
},
```

### Output

The `output` settings affect winget interface output behavior.

#### locale

The `locale` setting overrides winget interface language using a BCP47 language tag (for example, `en-US`). If not specified, winget uses the default Windows globalization behavior.

> [!NOTE]
> This setting only affects winget interface strings and does not affect package metadata localization or installer locale selection.

```json
"output": {
"locale": "en-US"
},
```

### Telemetry

The `telemetry` settings control whether winget writes ETW events that may be sent to Microsoft on a default installation of Windows.
Expand Down Expand Up @@ -133,4 +150,3 @@ The `downloader` setting controls which code is used when downloading packages.
## Enabling Experimental features

To discover which experimental features are available, go to [https://aka.ms/winget-settings](https://aka.ms/winget-settings) where you can see the experimental features available to you.

6 changes: 6 additions & 0 deletions schemas/JSON/settings/settings.schema.0.2.json
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,12 @@
"descending"
],
"default": "ascending"
},
"locale": {
"description": "Overrides winget interface output language using a BCP47 language tag",
"type": "string",
"pattern": "^([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$",
"maxLength": 20
}
}
}
Expand Down
31 changes: 31 additions & 0 deletions src/AppInstallerCLICore/Core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "COMContext.h"
#include <AppInstallerFileLogger.h>
#include <winget/OutputDebugStringLogger.h>
#include <winrt/Windows.Globalization.h>
#include "Public/ShutdownMonitoring.h"

#ifndef AICLI_DISABLE_TEST_HOOKS
Expand Down Expand Up @@ -78,6 +79,18 @@ namespace AppInstaller::CLI
main.Wait = WaitOnMainWaitEvent;
ShutdownMonitoring::ServerShutdownSynchronization::AddComponent(main);
}

std::optional<std::string> ApplyOutputLocaleOverride()
{
std::string localePreference = Settings::User().Get<Settings::Setting::OutputLocale>();
if (!localePreference.empty())
{
winrt::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride(Utility::ConvertToUTF16(localePreference));
return localePreference;
}

return {};
}
}

int CoreMain(int argc, wchar_t const** argv) try
Expand All @@ -89,6 +102,7 @@ namespace AppInstaller::CLI
std::signal(SIGABRT, abort_signal_handler);

init_apartment();
auto outputLocaleOverride = ApplyOutputLocaleOverride();

#ifndef AICLI_DISABLE_TEST_HOOKS
// We have to do this here so the auto minidump config initialization gets caught
Expand Down Expand Up @@ -120,6 +134,11 @@ namespace AppInstaller::CLI
Logging::OutputDebugStringLogger::Remove();
Logging::EnableWilFailureTelemetry();

if (outputLocaleOverride)
{
AICLI_LOG(CLI, Info, << "Applied output locale override from settings: " << outputLocaleOverride.value());
}

// Set output to UTF8
ConsoleOutputCPRestore utf8CP(CP_UTF8);

Expand Down Expand Up @@ -212,6 +231,8 @@ namespace AppInstaller::CLI

void ServerInitialize()
{
auto outputLocaleOverride = ApplyOutputLocaleOverride();

#ifndef AICLI_DISABLE_TEST_HOOKS
// We have to do this here so the auto minidump config initialization gets caught
Logging::OutputDebugStringLogger::Add();
Expand All @@ -227,10 +248,16 @@ namespace AppInstaller::CLI
#endif

AppInstaller::CLI::Execution::COMContext::SetLoggers();
if (outputLocaleOverride)
{
AICLI_LOG(CLI, Info, << "Applied output locale override from settings: " << outputLocaleOverride.value());
}
}

void InProcInitialize()
{
auto outputLocaleOverride = ApplyOutputLocaleOverride();

#ifndef AICLI_DISABLE_TEST_HOOKS
// We have to do this here so the auto minidump config initialization gets caught
Logging::OutputDebugStringLogger::Add();
Expand All @@ -247,5 +274,9 @@ namespace AppInstaller::CLI

// Explicitly set default channel and level before user settings from PackageManagerSettings
AppInstaller::CLI::Execution::COMContext::SetLoggers(AppInstaller::Logging::Channel::Defaults, AppInstaller::Logging::Level::Info);
if (outputLocaleOverride)
{
AICLI_LOG(CLI, Info, << "Applied output locale override from settings: " << outputLocaleOverride.value());
}
}
}
10 changes: 10 additions & 0 deletions src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj

@Trenly Trenly Jun 21, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes to the wapproj were required for me to be able to verify the functionality locally with wingetdev -?

Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,25 @@
<SubType>Designer</SubType>
</PRIResource>
<PRIResource Include="shared\strings\de-DE\winget.resw" Condition="Exists('shared\strings\de-DE\winget.resw')" />
<PRIResource Include="..\..\Localization\Resources\de-DE\winget.resw" Condition="!Exists('shared\strings\de-DE\winget.resw') and Exists('..\..\Localization\Resources\de-DE\winget.resw')" />
<PRIResource Include="shared\strings\es-ES\winget.resw" Condition="Exists('shared\strings\es-ES\winget.resw')" />
<PRIResource Include="..\..\Localization\Resources\es-ES\winget.resw" Condition="!Exists('shared\strings\es-ES\winget.resw') and Exists('..\..\Localization\Resources\es-ES\winget.resw')" />
<PRIResource Include="shared\strings\fr-FR\winget.resw" Condition="Exists('shared\strings\fr-FR\winget.resw')" />
<PRIResource Include="..\..\Localization\Resources\fr-FR\winget.resw" Condition="!Exists('shared\strings\fr-FR\winget.resw') and Exists('..\..\Localization\Resources\fr-FR\winget.resw')" />
<PRIResource Include="shared\strings\it-IT\winget.resw" Condition="Exists('shared\strings\it-IT\winget.resw')" />
<PRIResource Include="..\..\Localization\Resources\it-IT\winget.resw" Condition="!Exists('shared\strings\it-IT\winget.resw') and Exists('..\..\Localization\Resources\it-IT\winget.resw')" />
<PRIResource Include="shared\strings\ja-JP\winget.resw" Condition="Exists('shared\strings\ja-JP\winget.resw')" />
<PRIResource Include="..\..\Localization\Resources\ja-JP\winget.resw" Condition="!Exists('shared\strings\ja-JP\winget.resw') and Exists('..\..\Localization\Resources\ja-JP\winget.resw')" />
<PRIResource Include="shared\strings\ko-KR\winget.resw" Condition="Exists('shared\strings\ko-KR\winget.resw')" />
<PRIResource Include="..\..\Localization\Resources\ko-KR\winget.resw" Condition="!Exists('shared\strings\ko-KR\winget.resw') and Exists('..\..\Localization\Resources\ko-KR\winget.resw')" />
<PRIResource Include="shared\strings\pt-BR\winget.resw" Condition="Exists('shared\strings\pt-BR\winget.resw')" />
<PRIResource Include="..\..\Localization\Resources\pt-BR\winget.resw" Condition="!Exists('shared\strings\pt-BR\winget.resw') and Exists('..\..\Localization\Resources\pt-BR\winget.resw')" />
<PRIResource Include="shared\strings\ru-RU\winget.resw" Condition="Exists('shared\strings\ru-RU\winget.resw')" />
<PRIResource Include="..\..\Localization\Resources\ru-RU\winget.resw" Condition="!Exists('shared\strings\ru-RU\winget.resw') and Exists('..\..\Localization\Resources\ru-RU\winget.resw')" />
<PRIResource Include="shared\strings\zh-CN\winget.resw" Condition="Exists('shared\strings\zh-CN\winget.resw')" />
<PRIResource Include="..\..\Localization\Resources\zh-CN\winget.resw" Condition="!Exists('shared\strings\zh-CN\winget.resw') and Exists('..\..\Localization\Resources\zh-CN\winget.resw')" />
<PRIResource Include="shared\strings\zh-TW\winget.resw" Condition="Exists('shared\strings\zh-TW\winget.resw')" />
<PRIResource Include="..\..\Localization\Resources\zh-TW\winget.resw" Condition="!Exists('shared\strings\zh-TW\winget.resw') and Exists('..\..\Localization\Resources\zh-TW\winget.resw')" />
<PRIResource Include="Shared\Strings\en-us\Resources.resw" />
</ItemGroup>
<ItemGroup>
Expand Down
40 changes: 40 additions & 0 deletions src/AppInstallerCLITests/UserSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,46 @@ TEST_CASE("SettingOutputSortDirection", "[settings]")
}
}

TEST_CASE("SettingOutputLocale", "[settings]")
{
auto again = DeleteUserSettingsFiles();

SECTION("Default value")
{
UserSettingsTest userSettingTest;

REQUIRE(userSettingTest.Get<Setting::OutputLocale>().empty());
REQUIRE(userSettingTest.GetWarnings().size() == 0);
}
SECTION("Valid locale")
{
std::string_view json = R"({ "output": { "locale": "en-US" } })";
SetSetting(Stream::PrimaryUserSettings, json);
UserSettingsTest userSettingTest;

REQUIRE(userSettingTest.Get<Setting::OutputLocale>() == "en-US");
REQUIRE(userSettingTest.GetWarnings().size() == 0);
}
SECTION("Invalid locale")
{
std::string_view json = R"({ "output": { "locale": "en_US.UTF-8" } })";
SetSetting(Stream::PrimaryUserSettings, json);
UserSettingsTest userSettingTest;

REQUIRE(userSettingTest.Get<Setting::OutputLocale>().empty());
REQUIRE(userSettingTest.GetWarnings().size() == 1);
}
SECTION("Wrong type")
{
std::string_view json = R"({ "output": { "locale": ["en-US"] } })";
SetSetting(Stream::PrimaryUserSettings, json);
UserSettingsTest userSettingTest;

REQUIRE(userSettingTest.Get<Setting::OutputLocale>().empty());
REQUIRE(userSettingTest.GetWarnings().size() == 1);
}
}

TEST_CASE("ConvertToSortField", "[settings]")
{
SECTION("Valid values - lowercase")
Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCommonCore/Public/winget/UserSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ namespace AppInstaller::Settings
// Output behavior
OutputSortOrder,
OutputSortDirection,
OutputLocale,
#ifndef AICLI_DISABLE_TEST_HOOKS
// Debug
EnableSelfInitiatedMinidump,
Expand Down Expand Up @@ -242,6 +243,7 @@ namespace AppInstaller::Settings
// Output behavior
SETTINGMAPPING_SPECIALIZATION(Setting::OutputSortOrder, std::vector<std::string>, std::vector<SortField>, std::vector<SortField>{}, ".output.sortOrder"sv);
SETTINGMAPPING_SPECIALIZATION(Setting::OutputSortDirection, std::string, SortDirection, SortDirection::Ascending, ".output.sortDirection"sv);
SETTINGMAPPING_SPECIALIZATION(Setting::OutputLocale, std::string, std::string, {}, ".output.locale"sv);

// Used to deduce the SettingVariant type; making a variant that includes std::monostate and all SettingMapping types.
template <size_t... I>
Expand Down
10 changes: 10 additions & 0 deletions src/AppInstallerCommonCore/UserSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,16 @@ namespace AppInstaller::Settings

return {};
}

WINGET_VALIDATE_SIGNATURE(OutputLocale)
{
if (Locale::IsWellFormedBcp47Tag(value))
{
return value;
}

return {};
}
}

#ifndef AICLI_DISABLE_TEST_HOOKS
Expand Down
Loading