Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a language switcher using PrimaryLanguageOverride #10309

Merged
7 commits merged into from
Jun 10, 2021
Merged
Show file tree
Hide file tree
Changes from 6 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
3 changes: 3 additions & 0 deletions .github/actions/spelling/allow/allow.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ downsides
dze
dzhe
Enum'd
formattings
ftp
geeksforgeeks
ghe
Expand All @@ -34,6 +35,7 @@ overlined
postmodern
ptys
qof
qps
reimplementation
reserialization
reserialize
Expand All @@ -46,6 +48,7 @@ tokenizes
tonos
tshe
UIs
und
versioned
We'd
wildcards
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,6 @@
"**/bin/**": true,
"**/obj/**": true,
"**/packages/**": true,
"**/generated files/**": true
"**/Generated Files/**": true
}
}
5 changes: 5 additions & 0 deletions doc/cascadia/profiles.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,11 @@
},
"type": "array"
},
"language": {
"default": "",
"description": "Sets an override for the app's preferred language, expressed as a BCP-47 language tag like en-US.",
"type": "string"
},
"theme": {
"default": "system",
"description": "Sets the theme of the application. The special value \"system\" refers to the active Windows system theme.",
Expand Down
15 changes: 15 additions & 0 deletions src/cascadia/TerminalApp/AppLogic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ namespace winrt::TerminalApp::implementation
});
_root->Create();

_ApplyLanguageSettingChange();
_ApplyTheme(_settings.GlobalSettings().Theme());
_ApplyStartupTaskStateChange();

Expand Down Expand Up @@ -904,6 +905,19 @@ namespace winrt::TerminalApp::implementation
}
}

void AppLogic::_ApplyLanguageSettingChange()
{
using ApplicationLanguages = winrt::Windows::Globalization::ApplicationLanguages;

const auto language = _settings.GlobalSettings().Language();
const auto primaryLanguageOverride = ApplicationLanguages::PrimaryLanguageOverride();

if (primaryLanguageOverride != language)
{
ApplicationLanguages::PrimaryLanguageOverride(language);
}
}

fire_and_forget AppLogic::_LoadErrorsDialogRoutine()
{
co_await winrt::resume_foreground(_root->Dispatcher());
Expand Down Expand Up @@ -1023,6 +1037,7 @@ namespace winrt::TerminalApp::implementation
// Update the settings in TerminalPage
_root->SetSettings(_settings, true);

_ApplyLanguageSettingChange();
_RefreshThemeRoutine();
_ApplyStartupTaskStateChange();

Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/AppLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ namespace winrt::TerminalApp::implementation
bool _IsKeyboardServiceEnabled();
void _ShowKeyboardServiceDisabledDialog();

void _ApplyLanguageSettingChange();
fire_and_forget _LoadErrorsDialogRoutine();
fire_and_forget _ShowLoadWarningsDialogRoutine();
fire_and_forget _RefreshThemeRoutine();
Expand Down
45 changes: 18 additions & 27 deletions src/cascadia/TerminalApp/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,40 +25,39 @@

#include <wil/cppwinrt.h>

#include <unknwn.h>

#include <hstring.h>
Copy link
Member Author

Choose a reason for hiding this comment

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

Since I had to add <winrt/Windows.Globalization.h> to some pch.h files I decided to cleaned them up by removing unused includes and ordering and reordered the includes alphabetically.

Copy link
Member

Choose a reason for hiding this comment

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

be careful! you must include unknwn and hstring BEFORE any c++/winrt headers; wil is also sensitive to include order (!)

Copy link
Member Author

Choose a reason for hiding this comment

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

I removed those entirely, because it compiles without them.


#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Foundation.Metadata.h>
#include <winrt/Windows.Globalization.h>
#include <winrt/Windows.Graphics.Display.h>
#include <winrt/windows.ui.core.h>
#include <winrt/Windows.ui.input.h>
#include <winrt/Windows.System.h>
#include <winrt/Windows.UI.Core.h>
#include <winrt/Windows.UI.Input.h>
#include <winrt/Windows.UI.Text.h>
#include <winrt/Windows.UI.ViewManagement.h>
#include <winrt/Windows.UI.Xaml.Automation.Peers.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
#include <winrt/Windows.UI.Xaml.Data.h>
#include <winrt/Windows.ui.xaml.media.h>
#include <winrt/Windows.UI.Xaml.Documents.h>
#include <winrt/Windows.UI.Xaml.Input.h>
#include <winrt/Windows.UI.Xaml.Markup.h>
#include <winrt/Windows.UI.Xaml.Media.h>
#include <winrt/Windows.UI.Xaml.Media.Animation.h>
#include <winrt/Windows.ui.xaml.input.h>
#include <winrt/Windows.UI.Xaml.Hosting.h>
#include "winrt/Windows.UI.Xaml.Markup.h"
#include "winrt/Windows.UI.Xaml.Documents.h"
#include "winrt/Windows.UI.Xaml.Automation.h"
#include "winrt/Windows.UI.Xaml.Automation.Peers.h"
#include "winrt/Windows.UI.ViewManagement.h"
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.ApplicationModel.DataTransfer.h>

#include <winrt/Microsoft.Toolkit.Win32.UI.XamlHost.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>

#include <windows.ui.xaml.media.dxinterop.h>
#include <winrt/Microsoft.Terminal.Core.h>
#include <winrt/Microsoft.Terminal.Control.h>
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
#include <winrt/Microsoft.Terminal.Settings.Editor.h>
#include <winrt/Microsoft.Terminal.Settings.Model.h>

#include <winrt/Windows.System.h>
#include <windows.ui.xaml.media.dxinterop.h>

// Including TraceLogging essentials for the binary
#include <TraceLoggingProvider.h>
Expand All @@ -70,14 +69,6 @@ TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider);
#include <shellapi.h>
#include <shobjidl_core.h>

#include <winrt/Microsoft.Terminal.Core.h>
#include <winrt/Microsoft.Terminal.Control.h>
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
#include <winrt/Microsoft.Terminal.Settings.Editor.h>
#include <winrt/Microsoft.Terminal.Settings.Model.h>

#include <winrt/Windows.UI.Popups.h>

#include <CLI11/CLI11.hpp>

// Manually include til after we include Windows.Foundation to give it winrt superpowers
Expand Down
144 changes: 143 additions & 1 deletion src/cascadia/TerminalSettingsEditor/GlobalAppearance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
// Licensed under the MIT license.

#include "pch.h"
#include "EnumEntry.h"
#include "GlobalAppearance.h"
#include "GlobalAppearance.g.cpp"
#include "GlobalAppearancePageNavigationState.g.cpp"
#include "EnumEntry.h"

#include <LibraryResources.h>
Copy link
Member

Choose a reason for hiding this comment

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

This should be part of the precompiled header precomp.h or pch.h and not be necessary here

<numeric> should be in LibraryIncludes

Copy link
Member Author

Choose a reason for hiding this comment

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

Are you sure putting it into <LibraryIncludes> or the pch.h file is a good idea?
<numeric> is basically only used in 2 places throughout the project.


using namespace winrt;
using namespace winrt::Windows::UI::Xaml;
Expand All @@ -16,6 +18,11 @@ using namespace winrt::Windows::Foundation::Collections;

namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
// For ComboBox an empty SelectedItem string denotes no selection.
// What we want instead is for "Use system language" to be selected by default.
// --> "und" is synonymous for "Use system language".
constexpr std::wstring_view systemLanguageTag{ L"und" };

GlobalAppearance::GlobalAppearance()
{
InitializeComponent();
Expand All @@ -28,4 +35,139 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
_State = e.Parameter().as<Editor::GlobalAppearancePageNavigationState>();
}

winrt::hstring GlobalAppearance::LanguageDisplayConverter(const winrt::hstring& tag)
{
if (tag == systemLanguageTag)
{
return RS_(L"Globals_LanguageDefault");
}

winrt::Windows::Globalization::Language language{ tag };
return language.NativeName();
}

// Returns the list of languages the user may override the application language with.
// The returned list are BCP 47 language tags like {"und", "en-US", "de-DE", "es-ES", ...}.
// "und" is short for "undefined" and is synonymous for "Use system language" in this code.
winrt::Windows::Foundation::Collections::IObservableVector<winrt::hstring> GlobalAppearance::LanguageList()
{
if (_languageList)
{
return _languageList;
}

// In order to return the language list this code does the following:
// [1] Get all possible languages we want to allow the user to choose.
// We have to acquire languages from multiple sources, creating duplicates. See below at [1].
// [2] Sort languages by their ASCII tags, forcing the UI in a consistent/stable order.
// I wanted to sort the localized language names initially, but it turned out to be complex.
// [3] Remove potential duplicates in our language list from [1].
// We don't want to have en-US twice in the list, do we?
// [4] Optionally remove unwanted language tags (like pseudo-localizations).

std::vector<winrt::hstring> tags;

// [1]:
{
// ManifestLanguages contains languages the app ships with.
//
// Languages is a computed list that merges the ManifestLanguages with the
// user's ranked list of preferred languages taken from the system settings.
// As is tradition the API documentation is incomplete though, as it can also
// contain regional language variants. If our app supports en-US, but the user
// has en-GB or en-DE in their system's preferred language list, Languages will
// contain those as well, as they're variants from a supported language. We should
// allow a user to select those, as regional formattings can vary significantly.
const std::array tagSources{
winrt::Windows::Globalization::ApplicationLanguages::ManifestLanguages(),
winrt::Windows::Globalization::ApplicationLanguages::Languages()
};

// tags will hold all the flattened results from tagSources.
// We resize() the vector to the proper size in order to efficiently GetMany() all items.
tags.resize(std::accumulate(
tagSources.begin(),
tagSources.end(),
// tags[0] will be "und" - the "Use system language" item
// tags[1..n] will contain tags from tagSources.
// --> totalTags is offset by 1
1,
[](uint32_t sum, const auto& v) -> uint32_t {
return sum + v.Size();
}));

// As per the function definition, the first item
// is always "Use system language" ("und").
auto data = tags.data();
*data++ = systemLanguageTag;

// Finally GetMany() all the tags from tagSources.
for (const auto& v : tagSources)
{
const auto size = v.Size();
v.GetMany(0, winrt::array_view(data, size));
data += size;
}
}

// NOTE: The size of tags is always >0, due to tags[0] being hardcoded to "und".
const auto tagsBegin = ++tags.begin();
const auto tagsEnd = tags.end();

// [2]:
std::sort(tagsBegin, tagsEnd);

// I'd love for both, std::unique and std::remove_if, to occur in a single loop,
// but the code turned out to be complex and even less maintainable, so I gave up.
{
// [3] part 1:
auto it = std::unique(tagsBegin, tagsEnd);

// The qps- languages are useful for testing ("pseudo-localization").
// --> Leave them in if debug features are enabled.
if (!_State.Globals().DebugFeaturesEnabled())
{
// [4] part 1:
it = std::remove_if(it, tagsEnd, [](const winrt::hstring& tag) mutable -> bool {
Copy link
Member Author

Choose a reason for hiding this comment

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

@DHowett FYI I just realized this is a programming error and should be std::remove_if(tagsBegin, it, ...) instead. std::unique returns an iterator for the new end of the range, which I have to use instead of tagsEnd here.

return til::starts_with(tag, L"qps-");
});
}

// [3], [4] part 2 (completing the so called "erase-remove idiom"):
tags.erase(it, tagsEnd);
}

_languageList = winrt::single_threaded_observable_vector(std::move(tags));
return _languageList;
}

winrt::Windows::Foundation::IInspectable GlobalAppearance::CurrentLanguage()
{
if (_currentLanguage.empty())
{
_currentLanguage = winrt::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride();
if (_currentLanguage.empty())
{
_currentLanguage = systemLanguageTag;
}
}

return winrt::box_value(_currentLanguage);
}

void GlobalAppearance::CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag)
{
_currentLanguage = winrt::unbox_value<winrt::hstring>(tag);

const auto globals = _State.Globals();
if (_currentLanguage == systemLanguageTag)
{
globals.ClearLanguage();
}
else
{
globals.Language(_currentLanguage);
}
}
}
17 changes: 16 additions & 1 deletion src/cascadia/TerminalSettingsEditor/GlobalAppearance.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,24 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e);

WINRT_PROPERTY(Editor::GlobalAppearancePageNavigationState, State, nullptr);

GETSET_BINDABLE_ENUM_SETTING(Theme, winrt::Windows::UI::Xaml::ElementTheme, State().Globals, Theme);
GETSET_BINDABLE_ENUM_SETTING(TabWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, State().Globals, TabWidthMode);

public:
// LanguageDisplayConverter maps the given BCP 47 tag to a localized string.
// For instance "en-US" produces "English (United States)", while "de-DE" produces
// "Deutsch (Deutschland)". This works independently of the user's locale.
static winrt::hstring LanguageDisplayConverter(const winrt::hstring& tag);

winrt::Windows::Foundation::Collections::IObservableVector<winrt::hstring> LanguageList();
winrt::Windows::Foundation::IInspectable CurrentLanguage();
void CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag);

private:
std::vector<winrt::hstring> _GetSupportedLanguageTags();

winrt::Windows::Foundation::Collections::IObservableVector<winrt::hstring> _languageList{ nullptr };
winrt::hstring _currentLanguage;
};
}

Expand Down
6 changes: 5 additions & 1 deletion src/cascadia/TerminalSettingsEditor/GlobalAppearance.idl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import "EnumEntry.idl";
import "EnumEntry.idl";

namespace Microsoft.Terminal.Settings.Editor
{
Expand All @@ -15,6 +15,10 @@ namespace Microsoft.Terminal.Settings.Editor
GlobalAppearance();
GlobalAppearancePageNavigationState State { get; };

static String LanguageDisplayConverter(String tag);
Windows.Foundation.Collections.IObservableVector<String> LanguageList { get; };
IInspectable CurrentLanguage;

IInspectable CurrentTheme;
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> ThemeList { get; };

Expand Down
16 changes: 14 additions & 2 deletions src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,21 @@

<ScrollViewer>
<StackPanel Style="{StaticResource SettingsStackStyle}">
<!-- Theme -->
<local:SettingContainer x:Uid="Globals_Theme"
<!-- Language -->
<local:SettingContainer x:Uid="Globals_Language"
Copy link
Member Author

Choose a reason for hiding this comment

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

I wasn't sure where I should put the ComboBox. Would it look better elsewhere? Maybe not as the first item in the Appearances section?
The reason I put it as the first item was simply because I thought it looked nicer that way.

Copy link
Member

Choose a reason for hiding this comment

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

I think this is fine for now. Ideally, this would go into some kind of "General" page, but we're just not there yet imo.

@cinnamon-msft thoughts?

Margin="0">
<ComboBox ItemsSource="{x:Bind LanguageList}"
SelectedItem="{x:Bind CurrentLanguage, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock Text="{x:Bind local:GlobalAppearance.LanguageDisplayConverter((x:String))}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</local:SettingContainer>

<!-- Theme -->
<local:SettingContainer x:Uid="Globals_Theme">
<muxc:RadioButtons ItemTemplate="{StaticResource EnumRadioButtonTemplate}"
ItemsSource="{x:Bind ThemeList, Mode=OneWay}"
SelectedItem="{x:Bind CurrentTheme, Mode=TwoWay}" />
Expand Down
Loading