This commit is contained in:
Vlad Erium 2025-08-20 19:29:29 +08:00 committed by GitHub
commit d34c68a7da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 384 additions and 1 deletions

View file

@ -74,8 +74,27 @@ void LocalizationService::OverrideWithLanguage(_In_ const wchar_t* const languag
/// <param name="overridedLanguage">RFC-5646 identifier of the language to use, if null, will use the current language of the system</param> /// <param name="overridedLanguage">RFC-5646 identifier of the language to use, if null, will use the current language of the system</param>
LocalizationService::LocalizationService(_In_ const wchar_t * const overridedLanguage) LocalizationService::LocalizationService(_In_ const wchar_t * const overridedLanguage)
{ {
using namespace Windows::System::UserProfile;
m_isLanguageOverrided = overridedLanguage != nullptr; m_isLanguageOverrided = overridedLanguage != nullptr;
m_language = m_isLanguageOverrided ? ref new Platform::String(overridedLanguage) : ApplicationLanguages::Languages->GetAt(0); if (m_isLanguageOverrided)
{
m_language = ref new Platform::String(overridedLanguage);
}
else
{
// Prefer the system Display Language over Regional Settings
auto displayLanguages = GlobalizationPreferences::Languages;
if (displayLanguages != nullptr && displayLanguages->Size > 0)
{
m_language = ref new Platform::String(displayLanguages->GetAt(0)->Data());
}
else
{
// Fallback to the default application language list
m_language = ApplicationLanguages::Languages->GetAt(0);
}
}
m_flowDirection = ResourceContext::GetForViewIndependentUse()->QualifierValues->Lookup(L"LayoutDirection") m_flowDirection = ResourceContext::GetForViewIndependentUse()->QualifierValues->Lookup(L"LayoutDirection")
!= L"LTR" ? FlowDirection::RightToLeft : FlowDirection::LeftToRight; != L"LTR" ? FlowDirection::RightToLeft : FlowDirection::LeftToRight;
wstring localeName = wstring(m_language->Data()); wstring localeName = wstring(m_language->Data());

View file

@ -35,6 +35,20 @@ namespace CalculatorApp
/// </summary> /// </summary>
public App() public App()
{ {
// Ensure we choose the UI language based on System Display Language
// before any resources are loaded or singletons are created.
// If user selected a forced language, apply it; otherwise use Display Language
var selected = LanguageHelper.SelectedLanguage;
if (!string.IsNullOrEmpty(selected) && !string.Equals(selected, "system", StringComparison.OrdinalIgnoreCase))
{
LanguageHelper.ApplyUserLanguageSelection(selected);
}
else
{
LanguageHelper.InitializeDisplayLanguage();
}
LanguageHelper.LogLanguageDebugInfo(); // Debug
InitializeComponent(); InitializeComponent();
NarratorNotifier.RegisterDependencyProperties(); NarratorNotifier.RegisterDependencyProperties();
@ -149,6 +163,18 @@ namespace CalculatorApp
// Place the frame in the current Window // Place the frame in the current Window
Window.Current.Content = rootFrame; Window.Current.Content = rootFrame;
ThemeHelper.InitializeAppTheme(); ThemeHelper.InitializeAppTheme();
// Initialize Calculator to use System Display Language instead of Regional Settings
// Re-apply language at view creation based on persisted selection
var selected = LanguageHelper.SelectedLanguage;
var effective = selected == "system" ? LanguageHelper.GetSystemDisplayLanguage() : selected;
if (!string.IsNullOrEmpty(effective))
{
LanguageHelper.ApplyViewResourceLanguages(effective);
LanguageHelper.ApplyGlobalResourceLanguages(effective);
}
LanguageHelper.LogLanguageDebugInfo(); // Debug
Window.Current.Activate(); Window.Current.Activate();
} }

View file

@ -0,0 +1,256 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Windows.Storage;
namespace CalculatorApp.Utils
{
/// <summary>
/// Helper class for managing application language settings
/// </summary>
public static class LanguageHelper
{
private const string SelectedLanguageKey = "SelectedLanguage"; // "system" or BCP-47 tag
/// <summary>
/// Initialize the application to use the System Display Language instead of Regional Settings
/// </summary>
public static void InitializeDisplayLanguage()
{
try
{
var displayLanguage = GetSystemDisplayLanguage();
if (!string.IsNullOrEmpty(displayLanguage))
{
Debug.WriteLine($"Setting Calculator to use Display Language: {displayLanguage}");
SetApplicationLanguage(displayLanguage);
}
else
{
Debug.WriteLine("Could not detect system display language, using default behavior");
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error initializing display language: {ex.Message}");
// Don't throw - let the app continue with default behavior
}
}
/// <summary>
/// Persisted selected language code. "system" means follow system (Display Language)
/// </summary>
public static string SelectedLanguage
{
get => ApplicationData.Current.LocalSettings.Values[SelectedLanguageKey]?.ToString() ?? "system";
set => ApplicationData.Current.LocalSettings.Values[SelectedLanguageKey] = value;
}
/// <summary>
/// Return available UI languages for selection (System Default + supported app languages)
/// </summary>
public static List<LanguageInfo> GetAvailableLanguages()
{
var list = new List<LanguageInfo>();
list.Add(new LanguageInfo { Code = "system", NativeName = "System Default" });
foreach (var code in GetSupportedLanguageCodes())
{
try
{
var lang = new Windows.Globalization.Language(code);
list.Add(new LanguageInfo { Code = code, NativeName = lang.NativeName });
}
catch
{
list.Add(new LanguageInfo { Code = code, NativeName = code });
}
}
return list;
}
private static IEnumerable<string> GetSupportedLanguageCodes()
{
// Full list based on src/Calculator/Resources/* folders
return new[]
{
"af-ZA","am-ET","ar-SA","az-Latn-AZ","bg-BG","ca-ES","cs-CZ","da-DK","de-DE","el-GR",
"en-GB","en-US","es-ES","es-MX","et-EE","eu-ES","fa-IR","fi-FI","fil-PH","fr-CA","fr-FR",
"gl-ES","he-IL","hi-IN","hr-HR","hu-HU","id-ID","is-IS","it-IT","ja-JP","kk-KZ","km-KH",
"kn-IN","ko-KR","lo-LA","lt-LT","lv-LV","mk-MK","ml-IN","ms-MY","nb-NO","nl-NL","pl-PL",
"pt-BR","pt-PT","ro-RO","ru-RU","sk-SK","sl-SI","sq-AL","sr-Latn-RS","sv-SE","ta-IN",
"te-IN","th-TH","tr-TR","uk-UA","vi-VN","zh-CN","zh-TW"
};
}
/// <summary>
/// Apply a user-selected language code (or "system") immediately and persist preference
/// </summary>
public static void ApplyUserLanguageSelection(string code)
{
SelectedLanguage = code;
string languageToUse = code == "system" ? GetSystemDisplayLanguage() : code;
if (string.IsNullOrEmpty(languageToUse))
{
Debug.WriteLine("No language resolved; skipping override");
return;
}
SetApplicationLanguage(languageToUse);
ApplyViewResourceLanguages(languageToUse);
ApplyGlobalResourceLanguages(languageToUse);
}
/// <summary>
/// Gets the system's Display Language (not Regional Settings)
/// </summary>
/// <returns>The primary display language code (e.g., "en-US", "ja-JP")</returns>
public static string GetSystemDisplayLanguage()
{
try
{
// Primary method: Get the user's preferred UI languages from GlobalizationPreferences
// This returns the Display Language, not Regional Settings
var userLanguages = Windows.System.UserProfile.GlobalizationPreferences.Languages;
if (userLanguages.Count > 0)
{
var primaryLanguage = userLanguages[0];
Debug.WriteLine($"Display Language from GlobalizationPreferences: {primaryLanguage}");
return primaryLanguage;
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error getting GlobalizationPreferences: {ex.Message}");
// Fallback method: Try ResourceContext
try
{
var resourceContext = Windows.ApplicationModel.Resources.Core.ResourceContext.GetForCurrentView();
if (resourceContext.Languages.Count > 0)
{
var primaryLanguage = resourceContext.Languages[0];
Debug.WriteLine($"Display Language from ResourceContext: {primaryLanguage}");
return primaryLanguage;
}
}
catch (Exception ex2)
{
Debug.WriteLine($"Error getting ResourceContext languages: {ex2.Message}");
}
}
return null;
}
/// <summary>
/// Sets the application language using ApplicationLanguages.PrimaryLanguageOverride
/// </summary>
/// <param name="languageCode">Language code (e.g., "en-US", "ja-JP")</param>
public static void SetApplicationLanguage(string languageCode)
{
try
{
// Set the primary language override for this application
// This overrides the default language resolution and forces the app to use the specified language
Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = languageCode;
Debug.WriteLine($"ApplicationLanguages.PrimaryLanguageOverride set to: {languageCode}");
}
catch (Exception ex)
{
Debug.WriteLine($"Error setting application language to {languageCode}: {ex.Message}");
throw;
}
}
/// <summary>
/// Apply the specified language to ResourceContext used outside of any view
/// </summary>
public static void ApplyGlobalResourceLanguages(string languageCode)
{
try
{
// Set process-wide qualifier for language
Windows.ApplicationModel.Resources.Core.ResourceContext.SetGlobalQualifierValue("Language", languageCode);
Debug.WriteLine($"Global qualifier 'Language' set to: {languageCode}");
}
catch (Exception ex)
{
Debug.WriteLine($"Error applying global resource language: {ex.Message}");
}
}
/// <summary>
/// Apply the specified language to ResourceContext for the current view
/// </summary>
public static void ApplyViewResourceLanguages(string languageCode)
{
try
{
var ctx = Windows.ApplicationModel.Resources.Core.ResourceContext.GetForCurrentView();
// Set qualifier value for this view
ctx.QualifierValues["Language"] = languageCode;
Debug.WriteLine($"View qualifier 'Language' set to: {languageCode}");
}
catch (Exception ex)
{
Debug.WriteLine($"Error applying view resource language: {ex.Message}");
}
}
/// <summary>
/// Clears the application language override (for debugging/testing)
/// </summary>
public static void ClearApplicationLanguageOverride()
{
try
{
Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = "";
Debug.WriteLine("ApplicationLanguages.PrimaryLanguageOverride cleared");
}
catch (Exception ex)
{
Debug.WriteLine($"Error clearing application language override: {ex.Message}");
}
}
/// <summary>
/// Gets debug information about current language settings
/// </summary>
public static void LogLanguageDebugInfo()
{
try
{
var currentOverride = Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride;
var systemLanguages = Windows.System.UserProfile.GlobalizationPreferences.Languages;
var appLanguages = Windows.Globalization.ApplicationLanguages.Languages;
Debug.WriteLine("=== Language Debug Info ===");
Debug.WriteLine($"PrimaryLanguageOverride: '{currentOverride}'");
Debug.WriteLine($"System Display Languages: {string.Join(", ", systemLanguages)}");
Debug.WriteLine($"Application Languages: {string.Join(", ", appLanguages)}");
Debug.WriteLine("===========================");
}
catch (Exception ex)
{
Debug.WriteLine($"Error getting language debug info: {ex.Message}");
}
}
}
public class LanguageInfo
{
public string Code { get; set; }
public string NativeName { get; set; }
public override string ToString()
{
return string.IsNullOrEmpty(NativeName) ? Code : NativeName;
}
}
}

View file

@ -109,6 +109,21 @@
</toolkit:SettingsExpander.Items> </toolkit:SettingsExpander.Items>
</toolkit:SettingsExpander> </toolkit:SettingsExpander>
<toolkit:SettingsExpander x:Name="LanguageExpander"
Header="Language"
Description="Choose application language (requires restart for full effect)">
<toolkit:SettingsExpander.HeaderIcon>
<FontIcon Glyph="&#xF2B7;"/>
</toolkit:SettingsExpander.HeaderIcon>
<toolkit:SettingsExpander.Items>
<toolkit:SettingsCard ContentAlignment="Left">
<ComboBox x:Name="LanguageComboBox"
MinWidth="220"
SelectionChanged="OnLanguageSelectionChanged"/>
</toolkit:SettingsCard>
</toolkit:SettingsExpander.Items>
</toolkit:SettingsExpander>
<TextBlock x:Name="AboutGroupTitle" <TextBlock x:Name="AboutGroupTitle"
Margin="0,30,0,4" Margin="0,30,0,4"
Style="{StaticResource BodyStrongTextBlockStyle}" Style="{StaticResource BodyStrongTextBlockStyle}"

View file

@ -13,6 +13,7 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Automation.Peers; using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Automation.Provider; using Windows.UI.Xaml.Automation.Provider;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using System.Collections.Generic;
// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 // The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
@ -21,6 +22,7 @@ namespace CalculatorApp
public sealed partial class Settings : UserControl public sealed partial class Settings : UserControl
{ {
private const string BUILD_YEAR = "2025"; private const string BUILD_YEAR = "2025";
private bool _isSettingLanguageProgrammatically = false;
public event Windows.UI.Xaml.RoutedEventHandler BackButtonClick; public event Windows.UI.Xaml.RoutedEventHandler BackButtonClick;
@ -48,6 +50,8 @@ namespace CalculatorApp
AboutExpander.Description = copyrightText; AboutExpander.Description = copyrightText;
InitializeContributeTextBlock(); InitializeContributeTextBlock();
InitializeLanguageSettings();
} }
private void OnThemeSelectionChanged(object sender, SelectionChangedEventArgs e) private void OnThemeSelectionChanged(object sender, SelectionChangedEventArgs e)
@ -73,6 +77,27 @@ namespace CalculatorApp
var currentTheme = ThemeHelper.RootTheme.ToString(); var currentTheme = ThemeHelper.RootTheme.ToString();
(ThemeRadioButtons.Items.Cast<RadioButton>().FirstOrDefault(c => c?.Tag?.ToString() == currentTheme)).IsChecked = true; (ThemeRadioButtons.Items.Cast<RadioButton>().FirstOrDefault(c => c?.Tag?.ToString() == currentTheme)).IsChecked = true;
// Initialize language selection to current preference
try
{
var items = LanguageComboBox.ItemsSource as List<CalculatorApp.Utils.LanguageInfo>;
var selected = CalculatorApp.Utils.LanguageHelper.SelectedLanguage;
if (items != null)
{
foreach (var it in items)
{
if (it.Code == selected)
{
_isSettingLanguageProgrammatically = true;
LanguageComboBox.SelectedItem = it;
_isSettingLanguageProgrammatically = false;
break;
}
}
}
}
catch { _isSettingLanguageProgrammatically = false; }
SetDefaultFocus(); SetDefaultFocus();
} }
@ -139,6 +164,48 @@ namespace CalculatorApp
ContributeRunAfterLink.Text = contributeTextAfterHyperlink; ContributeRunAfterLink.Text = contributeTextAfterHyperlink;
} }
private void InitializeLanguageSettings()
{
try
{
var list = CalculatorApp.Utils.LanguageHelper.GetAvailableLanguages();
LanguageComboBox.ItemsSource = list;
}
catch (Exception ex)
{
Debug.WriteLine($"Language init error: {ex.Message}");
}
}
private void OnLanguageSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_isSettingLanguageProgrammatically)
{
return;
}
if (e.AddedItems.Count == 0)
return;
if (e.AddedItems[0] is CalculatorApp.Utils.LanguageInfo lang)
{
var current = CalculatorApp.Utils.LanguageHelper.SelectedLanguage;
if (string.Equals(current, lang.Code, StringComparison.OrdinalIgnoreCase))
{
return; // no actual change
}
CalculatorApp.Utils.LanguageHelper.ApplyUserLanguageSelection(lang.Code);
// Inform user that full app restart may be required to update all resources
var title = AppResourceProvider.GetInstance().GetResourceString("LanguageChangeDialog/Title");
var message = AppResourceProvider.GetInstance().GetResourceString("LanguageChangeDialog/Message");
if (string.IsNullOrEmpty(title)) title = "Language Changed";
if (string.IsNullOrEmpty(message)) message = "Some UI may update after restart.";
_ = new ContentDialog { Title = title, Content = message, PrimaryButtonText = "OK" }.ShowAsync();
}
}
private void System_BackRequested(object sender, BackRequestedEventArgs e) private void System_BackRequested(object sender, BackRequestedEventArgs e)
{ {
if (!e.Handled && BackButton.IsEnabled) if (!e.Handled && BackButton.IsEnabled)