diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 46e1bd6b47..131d5fb850 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -1557,7 +1557,7 @@ Stubless STYLECHANGED STYLECHANGING subkeys -SUBLANG +sublang subquery Superbar sut diff --git a/.pipelines/versionSetting.ps1 b/.pipelines/versionSetting.ps1 index 59cb8f398f..fa37bfbcd7 100644 --- a/.pipelines/versionSetting.ps1 +++ b/.pipelines/versionSetting.ps1 @@ -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); diff --git a/PowerToys.sln b/PowerToys.sln index 5f831135cb..e91c2e3907 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -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 diff --git a/src/common/ManagedCommon/LanguageHelper.cs b/src/common/ManagedCommon/LanguageHelper.cs new file mode 100644 index 0000000000..90791a03bc --- /dev/null +++ b/src/common/ManagedCommon/LanguageHelper.cs @@ -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(data).LanguageTag; + } + catch (Exception) + { + } + } + + return string.Empty; + } + } +} diff --git a/src/common/interop/PowerToys.Interop.vcxproj b/src/common/interop/PowerToys.Interop.vcxproj index 8180339f37..88115f9eab 100644 --- a/src/common/interop/PowerToys.Interop.vcxproj +++ b/src/common/interop/PowerToys.Interop.vcxproj @@ -168,6 +168,11 @@ + + + {cc6e41ac-8174-4e8a-8d22-85dd7f4851df} + + diff --git a/src/common/utils/language_helper.h b/src/common/utils/language_helper.h new file mode 100644 index 0000000000..85448efef3 --- /dev/null +++ b/src/common/utils/language_helper.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +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; + } +} diff --git a/src/common/utils/resources.h b/src/common/utils/resources.h index 00caff6f70..5cd0b36204 100644 --- a/src/common/utils/resources.h +++ b/src/common/utils/resources.h @@ -4,33 +4,204 @@ #include #include -// Get a string from the resource file -inline std::wstring get_resource_string(UINT resource_id, HINSTANCE instance, const wchar_t* fallback) +#include + + +inline std::wstring get_english_fallback_string(UINT resource_id, HINSTANCE instance) { - wchar_t* text_ptr; - auto length = LoadStringW(instance, resource_id, reinterpret_cast(&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 { - // Try to load en-us string as the first fallback. - WORD english_language = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US); - ATL::CStringW english_string; + if (!english_string.LoadStringW(instance, resource_id, english_language)) + { + return {}; + } + } + catch (...) + { + 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 (!english_string.LoadStringW(instance, resource_id, english_language)) + if (!result.LoadStringW(instance, resource_id, languageID)) { - return fallback; + return {}; } } catch (...) { - return fallback; + return {}; } - return std::wstring(english_string); + 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 { - return { text_ptr, static_cast(length) }; + wchar_t* text_ptr; + auto length = LoadStringW(instance, resource_id, reinterpret_cast(&text_ptr), 0); + if (length == 0) + { + if (!english_string.empty()) + { + return std::wstring(english_string); + } + else + { + return fallback; + } + } + else + { + return { text_ptr, static_cast(length) }; + } } } diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs index 3e3fc9c4b5..3f990ef6fa 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs @@ -50,6 +50,12 @@ namespace AdvancedPaste /// 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) => diff --git a/src/modules/EnvironmentVariables/EnvironmentVariables/EnvironmentVariablesXAML/App.xaml.cs b/src/modules/EnvironmentVariables/EnvironmentVariables/EnvironmentVariablesXAML/App.xaml.cs index 61c8e33f17..bc6f5aa1da 100644 --- a/src/modules/EnvironmentVariables/EnvironmentVariables/EnvironmentVariablesXAML/App.xaml.cs +++ b/src/modules/EnvironmentVariables/EnvironmentVariables/EnvironmentVariablesXAML/App.xaml.cs @@ -44,6 +44,12 @@ namespace EnvironmentVariables /// 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) => diff --git a/src/modules/FileLocksmith/FileLocksmithContextMenu/pch.h b/src/modules/FileLocksmith/FileLocksmithContextMenu/pch.h index 885d5d62e4..46064ef044 100644 --- a/src/modules/FileLocksmith/FileLocksmithContextMenu/pch.h +++ b/src/modules/FileLocksmith/FileLocksmithContextMenu/pch.h @@ -7,6 +7,8 @@ #ifndef PCH_H #define PCH_H +#include + // add headers that you want to pre-compile here #include "framework.h" diff --git a/src/modules/FileLocksmith/FileLocksmithUI/FileLocksmithXAML/App.xaml.cs b/src/modules/FileLocksmith/FileLocksmithUI/FileLocksmithXAML/App.xaml.cs index 8e5ff669de..e6186c46c8 100644 --- a/src/modules/FileLocksmith/FileLocksmithUI/FileLocksmithXAML/App.xaml.cs +++ b/src/modules/FileLocksmith/FileLocksmithUI/FileLocksmithXAML/App.xaml.cs @@ -23,6 +23,12 @@ namespace FileLocksmithUI /// public App() { + string appLanguage = LanguageHelper.LoadLanguage(); + if (!string.IsNullOrEmpty(appLanguage)) + { + Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage; + } + Logger.InitializeLogger("\\File Locksmith\\FileLocksmithUI\\Logs"); this.InitializeComponent(); diff --git a/src/modules/Hosts/Hosts/HostsXAML/App.xaml.cs b/src/modules/Hosts/Hosts/HostsXAML/App.xaml.cs index 27ab33fc87..cd4d8b177f 100644 --- a/src/modules/Hosts/Hosts/HostsXAML/App.xaml.cs +++ b/src/modules/Hosts/Hosts/HostsXAML/App.xaml.cs @@ -38,6 +38,12 @@ namespace Hosts /// public App() { + string appLanguage = LanguageHelper.LoadLanguage(); + if (!string.IsNullOrEmpty(appLanguage)) + { + Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage; + } + InitializeComponent(); Host.HostInstance = Microsoft.Extensions.Hosting.Host. diff --git a/src/modules/MeasureTool/MeasureToolUI/MeasureToolXAML/App.xaml.cs b/src/modules/MeasureTool/MeasureToolUI/MeasureToolXAML/App.xaml.cs index 8e08ac0afd..728d541207 100644 --- a/src/modules/MeasureTool/MeasureToolUI/MeasureToolXAML/App.xaml.cs +++ b/src/modules/MeasureTool/MeasureToolUI/MeasureToolXAML/App.xaml.cs @@ -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(); } diff --git a/src/modules/PowerOCR/PowerOCR/App.xaml.cs b/src/modules/PowerOCR/PowerOCR/App.xaml.cs index 045a961e26..eab9acd8cd 100644 --- a/src/modules/PowerOCR/PowerOCR/App.xaml.cs +++ b/src/modules/PowerOCR/PowerOCR/App.xaml.cs @@ -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(); } diff --git a/src/modules/Workspaces/WorkspacesEditor/App.xaml.cs b/src/modules/Workspaces/WorkspacesEditor/App.xaml.cs index 2edfd5c4b8..df86a35ed9 100644 --- a/src/modules/Workspaces/WorkspacesEditor/App.xaml.cs +++ b/src/modules/Workspaces/WorkspacesEditor/App.xaml.cs @@ -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); diff --git a/src/modules/Workspaces/WorkspacesLauncherUI/App.xaml.cs b/src/modules/Workspaces/WorkspacesLauncherUI/App.xaml.cs index b4117e746d..a065918523 100644 --- a/src/modules/Workspaces/WorkspacesLauncherUI/App.xaml.cs +++ b/src/modules/Workspaces/WorkspacesLauncherUI/App.xaml.cs @@ -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); diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index 7dbdd753e8..9ecaccd3a7 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -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) diff --git a/src/modules/colorPicker/ColorPickerUI/App.xaml.cs b/src/modules/colorPicker/ColorPickerUI/App.xaml.cs index b90e231892..98646583be 100644 --- a/src/modules/colorPicker/ColorPickerUI/App.xaml.cs +++ b/src/modules/colorPicker/ColorPickerUI/App.xaml.cs @@ -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; diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs index 2edb096018..9de3baeeba 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs @@ -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(); diff --git a/src/modules/imageresizer/ui/App.xaml.cs b/src/modules/imageresizer/ui/App.xaml.cs index db5a43e905..33e2240614 100644 --- a/src/modules/imageresizer/ui/App.xaml.cs +++ b/src/modules/imageresizer/ui/App.xaml.cs @@ -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; } diff --git a/src/modules/imageresizer/ui/ImageResizerUI.csproj b/src/modules/imageresizer/ui/ImageResizerUI.csproj index b1f25927e8..3a7701607e 100644 --- a/src/modules/imageresizer/ui/ImageResizerUI.csproj +++ b/src/modules/imageresizer/ui/ImageResizerUI.csproj @@ -46,6 +46,7 @@ + diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/Dialog.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/Dialog.cpp index 3ded9a2a7e..812c0f05ed 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/Dialog.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/Dialog.cpp @@ -1,9 +1,7 @@ #include "pch.h" #include "Dialog.h" -using namespace winrt::Windows::Foundation; - -IAsyncOperation Dialog::PartialRemappingConfirmationDialog(XamlRoot root, std::wstring dialogTitle) +winrt::Windows::Foundation::IAsyncOperation Dialog::PartialRemappingConfirmationDialog(XamlRoot root, std::wstring dialogTitle) { ContentDialog confirmationDialog; confirmationDialog.XamlRoot(root); diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp index 5779f8739e..6bf0994e3f 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp @@ -24,8 +24,6 @@ #include "EditorConstants.h" #include -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 OrphanKeysConfirmationDialog( +static winrt::Windows::Foundation::IAsyncOperation OrphanKeysConfirmationDialog( KBMEditor::KeyboardManagerState& state, const std::vector& keys, XamlRoot root) @@ -90,7 +88,7 @@ static IAsyncOperation OrphanKeysConfirmationDialog( co_return res == ContentDialogResult::Primary; } -static IAsyncAction OnClickAccept(KBMEditor::KeyboardManagerState& keyboardManagerState, XamlRoot root, std::function ApplyRemappings) +static winrt::Windows::Foundation::IAsyncAction OnClickAccept(KBMEditor::KeyboardManagerState& keyboardManagerState, XamlRoot root, std::function ApplyRemappings) { ShortcutErrorType isSuccess = LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(SingleKeyRemapControl::singleKeyRemapBuffer); diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditShortcutsWindow.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditShortcutsWindow.cpp index 9656598b4e..c85c36cde2 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditShortcutsWindow.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditShortcutsWindow.cpp @@ -18,8 +18,6 @@ #include "EditorConstants.h" #include -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 ApplyRemappings) diff --git a/src/modules/launcher/PowerLauncher/App.xaml.cs b/src/modules/launcher/PowerLauncher/App.xaml.cs index 30613d2cf5..86255f3ad7 100644 --- a/src/modules/launcher/PowerLauncher/App.xaml.cs +++ b/src/modules/launcher/PowerLauncher/App.xaml.cs @@ -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) { diff --git a/src/modules/launcher/Wox.Plugin/PluginLoadContext.cs b/src/modules/launcher/Wox.Plugin/PluginLoadContext.cs index 03268dd49d..9f4bd29632 100644 --- a/src/modules/launcher/Wox.Plugin/PluginLoadContext.cs +++ b/src/modules/launcher/Wox.Plugin/PluginLoadContext.cs @@ -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 { diff --git a/src/modules/peek/Peek.UI/PeekXAML/App.xaml.cs b/src/modules/peek/Peek.UI/PeekXAML/App.xaml.cs index 9a9dd783aa..6df56d6f46 100644 --- a/src/modules/peek/Peek.UI/PeekXAML/App.xaml.cs +++ b/src/modules/peek/Peek.UI/PeekXAML/App.xaml.cs @@ -40,6 +40,12 @@ namespace Peek.UI /// public App() { + string appLanguage = LanguageHelper.LoadLanguage(); + if (!string.IsNullOrEmpty(appLanguage)) + { + Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage; + } + InitializeComponent(); Logger.InitializeLogger("\\Peek\\Logs"); diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/App.xaml.cpp b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/App.xaml.cpp index 415acad0d4..71c510e357 100644 --- a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/App.xaml.cpp +++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/App.xaml.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -33,6 +34,12 @@ const std::wstring moduleName = L"PowerRename"; /// 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 diff --git a/src/modules/powerrename/PowerRenameUILib/pch.h b/src/modules/powerrename/PowerRenameUILib/pch.h index 66720e53c3..f3b4f298f3 100644 --- a/src/modules/powerrename/PowerRenameUILib/pch.h +++ b/src/modules/powerrename/PowerRenameUILib/pch.h @@ -37,4 +37,5 @@ #include #include #include +#include #include diff --git a/src/modules/registrypreview/RegistryPreview/RegistryPreviewXAML/App.xaml.cs b/src/modules/registrypreview/RegistryPreview/RegistryPreviewXAML/App.xaml.cs index 01007f5610..a6a3898f0f 100644 --- a/src/modules/registrypreview/RegistryPreview/RegistryPreviewXAML/App.xaml.cs +++ b/src/modules/registrypreview/RegistryPreview/RegistryPreviewXAML/App.xaml.cs @@ -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 /// public App() { + string appLanguage = LanguageHelper.LoadLanguage(); + + if (!string.IsNullOrEmpty(appLanguage)) + { + Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage; + } + this.InitializeComponent(); } diff --git a/src/runner/settings_window.cpp b/src/runner/settings_window.cpp index c8829773f7..6430824bb1 100644 --- a/src/runner/settings_window.cpp +++ b/src/runner/settings_window.cpp @@ -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; } diff --git a/src/settings-ui/Settings.UI.Library/LanguageModel.cs b/src/settings-ui/Settings.UI.Library/LanguageModel.cs new file mode 100644 index 0000000000..1e9ef82ec6 --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/LanguageModel.cs @@ -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(data).LanguageTag; + } + catch (Exception) + { + } + } + + return string.Empty; + } + } +} diff --git a/src/settings-ui/Settings.UI.Library/OutGoingLanguageSettings.cs b/src/settings-ui/Settings.UI.Library/OutGoingLanguageSettings.cs new file mode 100644 index 0000000000..61a16eb778 --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/OutGoingLanguageSettings.cs @@ -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); + } + } +} diff --git a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs index 8d38ec94c3..a820502dcd 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs @@ -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; diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml index 3443c03e0f..95fa0c5794 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml @@ -229,6 +229,25 @@ + + + + + +