[General]Support language selection (#34971)

* Language setting

* spellcheck

* Set FileLocksmithContextMenu package version in AppManifest.xml

* Fix ambigious symbol build error

* Fix ambigious symbol build error #2

* Revert unneeded changes

* Improve perf

* try fix ci build
This commit is contained in:
Stefan Markovic 2024-09-25 22:20:15 +02:00 committed by GitHub
parent 2b4b55cfeb
commit 5b616c9eed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 754 additions and 25 deletions

View File

@ -1557,7 +1557,7 @@ Stubless
STYLECHANGED
STYLECHANGING
subkeys
SUBLANG
sublang
subquery
Superbar
sut

View File

@ -58,3 +58,12 @@ $imageResizerContextMenuAppManifestReadFileLocation = $imageResizerContextMenuAp
$imageResizerContextMenuAppManifest.Package.Identity.Version = $versionNumber + '.0'
Write-Host "ImageResizerContextMenu version" $imageResizerContextMenuAppManifest.Package.Identity.Version
$imageResizerContextMenuAppManifest.Save($imageResizerContextMenuAppManifestWriteFileLocation);
# Set FileLocksmithContextMenu package version in AppManifest.xml
$fileLocksmithContextMenuAppManifestWriteFileLocation = $PSScriptRoot + '/../src/modules/FileLocksmith/FileLocksmithContextMenu/AppxManifest.xml';
$fileLocksmithContextMenuAppManifestReadFileLocation = $fileLocksmithContextMenuAppManifestWriteFileLocation;
[XML]$fileLocksmithContextMenuAppManifest = Get-Content $fileLocksmithContextMenuAppManifestReadFileLocation
$fileLocksmithContextMenuAppManifest.Package.Identity.Version = $versionNumber + '.0'
Write-Host "FileLocksmithContextMenu version" $fileLocksmithContextMenuAppManifest.Package.Identity.Version
$fileLocksmithContextMenuAppManifest.Save($fileLocksmithContextMenuAppManifestWriteFileLocation);

View File

@ -273,6 +273,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643
src\common\utils\HDropIterator.h = src\common\utils\HDropIterator.h
src\common\utils\HttpClient.h = src\common\utils\HttpClient.h
src\common\utils\json.h = src\common\utils\json.h
src\common\utils\language_helper.h = src\common\utils\language_helper.h
src\common\utils\logger_helper.h = src\common\utils\logger_helper.h
src\common\utils\modulesRegistry.h = src\common\utils\modulesRegistry.h
src\common\utils\MsiUtils.h = src\common\utils\MsiUtils.h

View File

@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.IO.Abstractions;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ManagedCommon
{
public static class LanguageHelper
{
public const string SettingsFilePath = "\\Microsoft\\PowerToys\\";
public const string SettingsFile = "language.json";
internal sealed class OutGoingLanguageSettings
{
[JsonPropertyName("language")]
public string LanguageTag { get; set; }
}
public static string LoadLanguage()
{
FileSystem fileSystem = new FileSystem();
var localAppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var file = localAppDataDir + SettingsFilePath + SettingsFile;
if (fileSystem.File.Exists(file))
{
try
{
Stream inputStream = fileSystem.File.Open(file, FileMode.Open);
StreamReader reader = new StreamReader(inputStream);
string data = reader.ReadToEnd();
inputStream.Close();
reader.Dispose();
return JsonSerializer.Deserialize<OutGoingLanguageSettings>(data).LanguageTag;
}
catch (Exception)
{
}
}
return string.Empty;
}
}
}

View File

@ -168,6 +168,11 @@
<Midl Include="LayoutMapManaged.idl" />
<Midl Include="TwoWayPipeMessageIPCManaged.idl" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\version\version.vcxproj">
<Project>{cc6e41ac-8174-4e8a-8d22-85dd7f4851df}</Project>
</ProjectReference>
</ItemGroup>
<ImportGroup Label="ExtensionTargets" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View File

@ -0,0 +1,22 @@
#pragma once
#include <filesystem>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/utils/json.h>
namespace LanguageHelpers
{
inline std::wstring load_language()
{
std::filesystem::path languageJsonFilePath(PTSettingsHelper::get_root_save_folder_location() + L"\\language.json");
auto langJson = json::from_file(languageJsonFilePath.c_str());
if (!langJson.has_value())
{
return {};
}
std::wstring language = langJson->GetNamedString(L"language", L"").c_str();
return language;
}
}

View File

@ -4,34 +4,205 @@
#include <string>
#include <atlstr.h>
// Get a string from the resource file
inline std::wstring get_resource_string(UINT resource_id, HINSTANCE instance, const wchar_t* fallback)
#include <common/utils/language_helper.h>
inline std::wstring get_english_fallback_string(UINT resource_id, HINSTANCE instance)
{
wchar_t* text_ptr;
auto length = LoadStringW(instance, resource_id, reinterpret_cast<wchar_t*>(&text_ptr), 0);
if (length == 0)
{
// Try to load en-us string as the first fallback.
WORD english_language = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
ATL::CStringW english_string;
try
{
if (!english_string.LoadStringW(instance, resource_id, english_language))
{
return fallback;
return {};
}
}
catch (...)
{
return fallback;
return {};
}
return std::wstring(english_string);
}
inline std::wstring get_resource_string_language_override(UINT resource_id, HINSTANCE instance)
{
static std::wstring language = LanguageHelpers::load_language();
unsigned lang = LANG_ENGLISH;
unsigned sublang = SUBLANG_ENGLISH_US;
if (!language.empty())
{
// Language list taken from Resources.wxs
if (language == L"ar-SA")
{
lang = LANG_ARABIC;
sublang = SUBLANG_ARABIC_SAUDI_ARABIA;
}
else if (language == L"cs-CZ")
{
lang = LANG_CZECH;
sublang = SUBLANG_CZECH_CZECH_REPUBLIC;
}
else if (language == L"de-DE")
{
lang = LANG_GERMAN;
sublang = SUBLANG_GERMAN;
}
else if (language == L"en-US")
{
lang = LANG_ENGLISH;
sublang = SUBLANG_ENGLISH_US;
}
else if (language == L"es-ES")
{
lang = LANG_SPANISH;
sublang = SUBLANG_SPANISH;
}
else if (language == L"fa-IR")
{
lang = LANG_PERSIAN;
sublang = SUBLANG_PERSIAN_IRAN;
}
else if (language == L"fr-FR")
{
lang = LANG_FRENCH;
sublang = SUBLANG_FRENCH;
}
else if (language == L"he-IL")
{
lang = LANG_HEBREW;
sublang = SUBLANG_HEBREW_ISRAEL;
}
else if (language == L"hu-HU")
{
lang = LANG_HUNGARIAN;
sublang = SUBLANG_HUNGARIAN_HUNGARY;
}
else if (language == L"it-IT")
{
lang = LANG_ITALIAN;
sublang = SUBLANG_ITALIAN;
}
else if (language == L"ja-JP")
{
lang = LANG_JAPANESE;
sublang = SUBLANG_JAPANESE_JAPAN;
}
else if (language == L"ko-KR")
{
lang = LANG_KOREAN;
sublang = SUBLANG_KOREAN;
}
else if (language == L"nl-NL")
{
lang = LANG_DUTCH;
sublang = SUBLANG_DUTCH;
}
else if (language == L"pl-PL")
{
lang = LANG_POLISH;
sublang = SUBLANG_POLISH_POLAND;
}
else if (language == L"pt-BR")
{
lang = LANG_PORTUGUESE;
sublang = SUBLANG_PORTUGUESE_BRAZILIAN;
}
else if (language == L"pt-PT")
{
lang = LANG_PORTUGUESE;
sublang = SUBLANG_PORTUGUESE;
}
else if (language == L"ru-RU")
{
lang = LANG_RUSSIAN;
sublang = SUBLANG_RUSSIAN_RUSSIA;
}
else if (language == L"sv-SE")
{
lang = LANG_SWEDISH;
sublang = SUBLANG_SWEDISH;
}
else if (language == L"tr-TR")
{
lang = LANG_TURKISH;
sublang = SUBLANG_TURKISH_TURKEY;
}
else if (language == L"uk-UA")
{
lang = LANG_UKRAINIAN;
sublang = SUBLANG_UKRAINIAN_UKRAINE;
}
else if (language == L"zh-CN")
{
lang = LANG_CHINESE_SIMPLIFIED;
sublang = SUBLANG_CHINESE_SIMPLIFIED;
}
else if (language == L"zh-TW")
{
lang = LANG_CHINESE_TRADITIONAL;
sublang = SUBLANG_CHINESE_TRADITIONAL;
}
WORD languageID = MAKELANGID(lang, sublang);
ATL::CStringW result;
try
{
if (!result.LoadStringW(instance, resource_id, languageID))
{
return {};
}
}
catch (...)
{
return {};
}
if (!result.IsEmpty())
{
return std::wstring(result);
}
}
return {};
}
// Get a string from the resource file
inline std::wstring get_resource_string(UINT resource_id, HINSTANCE instance, const wchar_t* fallback)
{
// Try to load en-us string as the first fallback.
std::wstring english_string = get_english_fallback_string(resource_id, instance);
std::wstring language_override_resource = get_resource_string_language_override(resource_id, instance);
if (!language_override_resource.empty())
{
return language_override_resource;
}
else
{
wchar_t* text_ptr;
auto length = LoadStringW(instance, resource_id, reinterpret_cast<wchar_t*>(&text_ptr), 0);
if (length == 0)
{
if (!english_string.empty())
{
return std::wstring(english_string);
}
else
{
return fallback;
}
}
else
{
return { text_ptr, static_cast<std::size_t>(length) };
}
}
}
extern "C" IMAGE_DOS_HEADER __ImageBase;

View File

@ -50,6 +50,12 @@ namespace AdvancedPaste
/// </summary>
public App()
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
this.InitializeComponent();
Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder().UseContentRoot(AppContext.BaseDirectory).ConfigureServices((context, services) =>

View File

@ -44,6 +44,12 @@ namespace EnvironmentVariables
/// </summary>
public App()
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
this.InitializeComponent();
Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder().UseContentRoot(AppContext.BaseDirectory).ConfigureServices((context, services) =>

View File

@ -7,6 +7,8 @@
#ifndef PCH_H
#define PCH_H
#include <atlbase.h>
// add headers that you want to pre-compile here
#include "framework.h"

View File

@ -23,6 +23,12 @@ namespace FileLocksmithUI
/// </summary>
public App()
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
Logger.InitializeLogger("\\File Locksmith\\FileLocksmithUI\\Logs");
this.InitializeComponent();

View File

@ -38,6 +38,12 @@ namespace Hosts
/// </summary>
public App()
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
InitializeComponent();
Host.HostInstance = Microsoft.Extensions.Hosting.Host.

View File

@ -24,6 +24,12 @@ namespace MeasureToolUI
{
Logger.InitializeLogger("\\Measure Tool\\MeasureToolUI\\Logs");
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
this.InitializeComponent();
}

View File

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Threading;
using System.Windows;
@ -28,6 +29,19 @@ public partial class App : Application, IDisposable
{
Logger.InitializeLogger("\\TextExtractor\\Logs");
try
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage);
}
}
catch (CultureNotFoundException ex)
{
Logger.LogError("CultureNotFoundException: " + ex.Message);
}
NativeThreadCTS = new CancellationTokenSource();
}

View File

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Threading;
using System.Windows;
@ -40,6 +41,20 @@ namespace WorkspacesEditor
Logger.InitializeLogger("\\Workspaces\\Logs");
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
var languageTag = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(languageTag))
{
try
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(languageTag);
}
catch (CultureNotFoundException ex)
{
Logger.LogError("CultureNotFoundException: " + ex.Message);
}
}
const string appName = "Local\\PowerToys_Workspaces_Editor_InstanceMutex";
bool createdNew;
_instanceMutex = new Mutex(true, appName, out createdNew);

View File

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Threading;
using System.Windows;
using Common.UI;
@ -41,6 +42,20 @@ namespace WorkspacesLauncherUI
Logger.InitializeLogger("\\Workspaces\\WorkspacesLauncherUI");
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
var languageTag = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(languageTag))
{
try
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(languageTag);
}
catch (CultureNotFoundException ex)
{
Logger.LogError("CultureNotFoundException: " + ex.Message);
}
}
const string appName = "Local\\PowerToys_Workspaces_LauncherUI_InstanceMutex";
bool createdNew;
_instanceMutex = new Mutex(true, appName, out createdNew);

View File

@ -56,6 +56,19 @@ namespace Awake
Logger.InitializeLogger(Path.Combine("\\", Core.Constants.AppName, "Logs"));
try
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage);
}
}
catch (CultureNotFoundException ex)
{
Logger.LogError("CultureNotFoundException: " + ex.Message);
}
AppDomain.CurrentDomain.UnhandledException += AwakeUnhandledExceptionCatcher;
if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredAwakeEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)

View File

@ -4,6 +4,7 @@
using System;
using System.ComponentModel.Composition;
using System.Globalization;
using System.Threading;
using System.Windows;
@ -29,6 +30,19 @@ namespace ColorPickerUI
protected override void OnStartup(StartupEventArgs e)
{
try
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage);
}
}
catch (CultureNotFoundException ex)
{
Logger.LogError("CultureNotFoundException: " + ex.Message);
}
NativeThreadCTS = new CancellationTokenSource();
ExitToken = NativeThreadCTS.Token;

View File

@ -4,6 +4,7 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
using System.Windows;
using System.Windows.Input;
@ -55,6 +56,20 @@ namespace FancyZonesEditor
public App()
{
var languageTag = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(languageTag))
{
try
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(languageTag);
}
catch (CultureNotFoundException ex)
{
Logger.LogError("CultureNotFoundException: " + ex.Message);
}
}
Logger.InitializeLogger("\\FancyZones\\Editor\\Logs");
// DebugModeCheck();

View File

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
using System;
using System.Globalization;
using System.Text;
using System.Windows;
@ -19,6 +20,19 @@ namespace ImageResizer
{
static App()
{
try
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage);
}
}
catch (CultureNotFoundException)
{
// error
}
Console.InputEncoding = Encoding.Unicode;
}

View File

@ -46,6 +46,7 @@
<ProjectReference Include="..\..\..\common\GPOWrapperProjection\GPOWrapperProjection.csproj" />
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">

View File

@ -1,9 +1,7 @@
#include "pch.h"
#include "Dialog.h"
using namespace winrt::Windows::Foundation;
IAsyncOperation<bool> Dialog::PartialRemappingConfirmationDialog(XamlRoot root, std::wstring dialogTitle)
winrt::Windows::Foundation::IAsyncOperation<bool> Dialog::PartialRemappingConfirmationDialog(XamlRoot root, std::wstring dialogTitle)
{
ContentDialog confirmationDialog;
confirmationDialog.XamlRoot(root);

View File

@ -24,8 +24,6 @@
#include "EditorConstants.h"
#include <common/Themes/theme_listener.h>
using namespace winrt::Windows::Foundation;
static UINT g_currentDPI = DPIAware::DEFAULT_DPI;
LRESULT CALLBACK EditKeyboardWindowProc(HWND, UINT, WPARAM, LPARAM);
@ -57,7 +55,7 @@ static void handleTheme()
}
}
static IAsyncOperation<bool> OrphanKeysConfirmationDialog(
static winrt::Windows::Foundation::IAsyncOperation<bool> OrphanKeysConfirmationDialog(
KBMEditor::KeyboardManagerState& state,
const std::vector<DWORD>& keys,
XamlRoot root)
@ -90,7 +88,7 @@ static IAsyncOperation<bool> OrphanKeysConfirmationDialog(
co_return res == ContentDialogResult::Primary;
}
static IAsyncAction OnClickAccept(KBMEditor::KeyboardManagerState& keyboardManagerState, XamlRoot root, std::function<void()> ApplyRemappings)
static winrt::Windows::Foundation::IAsyncAction OnClickAccept(KBMEditor::KeyboardManagerState& keyboardManagerState, XamlRoot root, std::function<void()> ApplyRemappings)
{
ShortcutErrorType isSuccess = LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(SingleKeyRemapControl::singleKeyRemapBuffer);

View File

@ -18,8 +18,6 @@
#include "EditorConstants.h"
#include <common/Themes/theme_listener.h>
using namespace winrt::Windows::Foundation;
static UINT g_currentDPI = DPIAware::DEFAULT_DPI;
LRESULT CALLBACK EditShortcutsWindowProc(HWND, UINT, WPARAM, LPARAM);
@ -51,7 +49,7 @@ static void handleTheme()
}
}
static IAsyncAction OnClickAccept(
static winrt::Windows::Foundation::IAsyncAction OnClickAccept(
KBMEditor::KeyboardManagerState& keyboardManagerState,
XamlRoot root,
std::function<void()> ApplyRemappings)

View File

@ -17,6 +17,7 @@ using PowerLauncher.Helper;
using PowerLauncher.Plugin;
using PowerLauncher.ViewModel;
using PowerToys.Interop;
using Windows.Globalization;
using Wox;
using Wox.Infrastructure;
using Wox.Infrastructure.Image;
@ -54,6 +55,19 @@ namespace PowerLauncher
{
NativeThreadCTS = new CancellationTokenSource();
try
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage);
}
}
catch (CultureNotFoundException ex)
{
Logger.LogError("CultureNotFoundException: " + ex.Message);
}
Log.Info($"Starting PowerToys Run with PID={Environment.ProcessId}", typeof(App));
if (PowerToys.GPOWrapperProjection.GPOWrapper.GetConfiguredPowerLauncherEnabledValue() == PowerToys.GPOWrapperProjection.GpoRuleConfigured.Disabled)
{

View File

@ -3,9 +3,11 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using ManagedCommon;
namespace Wox.Plugin
{

View File

@ -40,6 +40,12 @@ namespace Peek.UI
/// </summary>
public App()
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
InitializeComponent();
Logger.InitializeLogger("\\Peek\\Logs");

View File

@ -9,6 +9,7 @@
#include <common/logger/logger.h>
#include <common/logger/logger_settings.h>
#include <common/utils/language_helper.h>
#include <common/utils/logger_helper.h>
#include <common/utils/gpo.h>
@ -33,6 +34,12 @@ const std::wstring moduleName = L"PowerRename";
/// </summary>
App::App()
{
std::wstring appLanguage = LanguageHelpers::load_language();
if (!appLanguage.empty())
{
Microsoft::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride(appLanguage);
}
InitializeComponent();
#if defined _DEBUG && !defined DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION

View File

@ -37,4 +37,5 @@
#include <winrt/Microsoft.UI.Xaml.Shapes.h>
#include <winrt/Microsoft.UI.Dispatching.h>
#include <winrt/Microsoft.Windows.ApplicationModel.Resources.h>
#include <winrt/Microsoft.Windows.Globalization.h>
#include <wil/cppwinrt_helpers.h>

View File

@ -5,6 +5,7 @@
using System;
using System.Web;
using ManagedCommon;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle;
using Windows.ApplicationModel.Activation;
@ -25,6 +26,13 @@ namespace RegistryPreview
/// </summary>
public App()
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
this.InitializeComponent();
}

View File

@ -233,6 +233,12 @@ void dispatch_received_json(const std::wstring& json_to_parse)
SendMessageW(pt_main_window, WM_CLOSE, 0, 0);
}
}
else if (name == L"language")
{
constexpr const wchar_t* language_filename = L"\\language.json";
const std::wstring save_file_location = PTSettingsHelper::get_root_save_folder_location() + language_filename;
json::to_file(save_file_location, j);
}
}
return;
}

View File

@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.IO.Abstractions;
using System.Text.Json;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class LanguageModel
{
public const string SettingsFilePath = "\\Microsoft\\PowerToys\\";
public const string SettingsFile = "language.json";
public string Tag { get; set; }
public string ResourceID { get; set; }
public string Language { get; set; }
public static string LoadSetting()
{
FileSystem fileSystem = new FileSystem();
var localAppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var file = localAppDataDir + SettingsFilePath + SettingsFile;
if (fileSystem.File.Exists(file))
{
try
{
Stream inputStream = fileSystem.File.Open(file, FileMode.Open);
StreamReader reader = new StreamReader(inputStream);
string data = reader.ReadToEnd();
inputStream.Close();
reader.Dispose();
return JsonSerializer.Deserialize<OutGoingLanguageSettings>(data).LanguageTag;
}
catch (Exception)
{
}
}
return string.Empty;
}
}
}

View File

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class OutGoingLanguageSettings
{
[JsonPropertyName("language")]
public string LanguageTag { get; set; }
public OutGoingLanguageSettings()
{
}
public OutGoingLanguageSettings(string language)
{
LanguageTag = language;
}
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}
}

View File

@ -78,6 +78,12 @@ namespace Microsoft.PowerToys.Settings.UI
{
Logger.InitializeLogger(@"\Settings\Logs");
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
InitializeComponent();
UnhandledException += App_UnhandledException;

View File

@ -229,6 +229,25 @@
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="Appearance_Behavior" IsEnabled="True">
<tkcontrols:SettingsCard x:Uid="LanguageHeader" HeaderIcon="{ui:FontIcon Glyph=&#xF2B7;}">
<ComboBox
x:Name="Languages_ComboBox"
MinWidth="{StaticResource SettingActionControlMinWidth}"
DisplayMemberPath="Language"
ItemsSource="{Binding Languages, Mode=TwoWay}"
SelectedIndex="{Binding LanguagesIndex, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="LanguageRestartInfo"
IsClosable="False"
IsOpen="{x:Bind Mode=OneWay, Path=ViewModel.LanguageChanged}"
IsTabStop="True"
Severity="Informational">
<InfoBar.ActionButton>
<Button x:Uid="LanguageRestartInfoButton" Click="Click_LanguageRestart" />
</InfoBar.ActionButton>
</InfoBar>
<tkcontrols:SettingsCard x:Uid="ColorModeHeader" HeaderIcon="{ui:FontIcon Glyph=&#xE790;}">
<tkcontrols:SettingsCard.Description>
<HyperlinkButton x:Uid="Windows_Color_Settings" Click="OpenColorsSettings_Click" />

View File

@ -130,5 +130,10 @@ namespace Microsoft.PowerToys.Settings.UI.Views
string r = await Task.FromResult<string>(ShellGetFolder.GetFolderDialog(hwnd));
return r;
}
private void Click_LanguageRestart(object sender, RoutedEventArgs e)
{
ViewModel.Restart();
}
}
}

View File

@ -59,7 +59,10 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:schema id="root"
xmlns=""
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
@ -4380,4 +4383,85 @@ Activate by holding the key for the character you want to add an accent to, then
<data name="OpenSettings.Content" xml:space="preserve">
<value>Open settings</value>
</data>
<data name="LanguageHeader.Header" xml:space="preserve">
<value>Application language</value>
</data>
<data name="LanguageHeader.Description" xml:space="preserve">
<value>PowerToys will use OS language by default.</value>
</data>
<data name="LanguageRestartInfo.Title" xml:space="preserve">
<value>Restart PowerToys for language change to take effect.</value>
</data>
<data name="LanguageRestartInfoButton.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="Default_Language" xml:space="preserve">
<value>Windows default</value>
</data>
<data name="Arabic_Saudi_Arabia_Language" xml:space="preserve">
<value>Arabic (Saudi Arabia)</value>
</data>
<data name="Czech_Language" xml:space="preserve">
<value>Czech</value>
</data>
<data name="German_Language" xml:space="preserve">
<value>German</value>
</data>
<data name="English_Language" xml:space="preserve">
<value>English</value>
</data>
<data name="Spanish_Language" xml:space="preserve">
<value>Spanish</value>
</data>
<data name="Persian_Farsi_Language" xml:space="preserve">
<value>Persian (Farsi)</value>
</data>
<data name="French_Language" xml:space="preserve">
<value>French</value>
</data>
<data name="Hebrew_Israel_Language" xml:space="preserve">
<value>Hebrew (Israel)</value>
</data>
<data name="Hungarian_Language" xml:space="preserve">
<value>Hungarian</value>
</data>
<data name="Italian_Language" xml:space="preserve">
<value>Italian</value>
</data>
<data name="Japanese_Language" xml:space="preserve">
<value>Japanese</value>
</data>
<data name="Korean_Language" xml:space="preserve">
<value>Korean</value>
</data>
<data name="Dutch_Language" xml:space="preserve">
<value>Dutch</value>
</data>
<data name="Polish_Language" xml:space="preserve">
<value>Polish</value>
</data>
<data name="Portuguese_Brazil_Language" xml:space="preserve">
<value>Portuguese (Brazil)</value>
</data>
<data name="Portuguese_Portugal_Language" xml:space="preserve">
<value>Portuguese (Portugal)</value>
</data>
<data name="Russian_Language" xml:space="preserve">
<value>Russian</value>
</data>
<data name="Swedish_Language" xml:space="preserve">
<value>Swedish</value>
</data>
<data name="Turkish_Language" xml:space="preserve">
<value>Turkish</value>
</data>
<data name="Ukrainian_Language" xml:space="preserve">
<value>Ukrainian</value>
</data>
<data name="Chinese_Simplified_Language" xml:space="preserve">
<value>Chinese (Simplified)</value>
</data>
<data name="Chinese_Traditional_Language" xml:space="preserve">
<value>Chinese (Traditional)</value>
</data>
</root>

View File

@ -3,10 +3,13 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
@ -145,8 +148,38 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
_fileWatcher = Helper.GetFileWatcher(string.Empty, UpdatingSettings.SettingsFile, dispatcherAction);
}
InitializeLanguages();
}
// Supported languages. Taken from Resources.wxs + default + en-US
private Dictionary<string, string> langTagsAndIds = new Dictionary<string, string>
{
{ string.Empty, "Default_language" },
{ "ar-SA", "Arabic_Saudi_Arabia_Language" },
{ "cs-CZ", "Czech_Language" },
{ "de-DE", "German_Language" },
{ "en-US", "English_Language" },
{ "es-ES", "Spanish_Language" },
{ "fa-IR", "Persian_Farsi_Language" },
{ "fr-FR", "French_Language" },
{ "he-IL", "Hebrew_Israel_Language" },
{ "hu-HU", "Hungarian_Language" },
{ "it-IT", "Italian_Language" },
{ "ja-JP", "Japanese_Language" },
{ "ko-KR", "Korean_Language" },
{ "nl-NL", "Dutch_Language" },
{ "pl-PL", "Polish_Language" },
{ "pt-BR", "Portuguese_Brazil_Language" },
{ "pt-PT", "Portuguese_Portugal_Language" },
{ "ru-RU", "Russian_Language" },
{ "sv-SE", "Swedish_Language" },
{ "tr-TR", "Turkish_Language" },
{ "uk-UA", "Ukrainian_Language" },
{ "zh-CN", "Chinese_Simplified_Language" },
{ "zh-TW", "Chinese_Traditional_Language" },
};
private static bool _isDevBuild;
private bool _startup;
private bool _isElevated;
@ -177,6 +210,10 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private string _settingsBackupMessage;
private string _backupRestoreMessageSeverity;
private int _languagesIndex;
private int _initLanguagesIndex;
private bool _languageChanged;
// Gets or sets a value indicating whether run powertoys on start-up.
public bool Startup
{
@ -740,6 +777,51 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public ObservableCollection<LanguageModel> Languages { get; } = new ObservableCollection<LanguageModel>();
public int LanguagesIndex
{
get
{
return _languagesIndex;
}
set
{
if (_languagesIndex != value)
{
_languagesIndex = value;
OnPropertyChanged(nameof(LanguagesIndex));
NotifyLanguageChanged();
if (_initLanguagesIndex != value)
{
LanguageChanged = true;
}
else
{
LanguageChanged = false;
}
}
}
}
public bool LanguageChanged
{
get
{
return _languageChanged;
}
set
{
if (_languageChanged != value)
{
_languageChanged = value;
OnPropertyChanged(nameof(LanguageChanged));
}
}
}
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null, bool reDoBackupDryRun = true)
{
// Notify UI of property change
@ -996,5 +1078,31 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
NotifyPropertyChanged(nameof(IsDownloadAllowed));
}
}
private void InitializeLanguages()
{
var lang = LanguageModel.LoadSetting();
int i = 0;
foreach (var item in langTagsAndIds)
{
Languages.Add(new LanguageModel { Tag = item.Key, ResourceID = item.Value, Language = GetResourceString(item.Value) });
if (item.Key.Equals(lang, StringComparison.Ordinal))
{
_initLanguagesIndex = i;
LanguagesIndex = i;
}
i++;
}
}
private void NotifyLanguageChanged()
{
OutGoingLanguageSettings outsettings = new OutGoingLanguageSettings(langTagsAndIds.ElementAt(LanguagesIndex).Key);
SendConfigMSG(outsettings.ToString());
}
}
}