555 lines
19 KiB
C++
555 lines
19 KiB
C++
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
#include "pch.h"
|
|
#include "LocalizationService.h"
|
|
#include "LocalizationSettings.h"
|
|
#include "AppResourceProvider.h"
|
|
|
|
using namespace CalculatorApp::Common;
|
|
using namespace CalculatorApp::Common::LocalizationServiceProperties;
|
|
using namespace Concurrency;
|
|
using namespace Platform;
|
|
using namespace Platform::Collections;
|
|
using namespace std;
|
|
using namespace Windows::ApplicationModel::Resources;
|
|
using namespace Windows::ApplicationModel::Resources::Core;
|
|
using namespace Windows::Foundation;
|
|
using namespace Windows::Foundation::Collections;
|
|
using namespace Windows::Globalization;
|
|
using namespace Windows::Globalization::DateTimeFormatting;
|
|
using namespace Windows::Globalization::Fonts;
|
|
using namespace Windows::Globalization::NumberFormatting;
|
|
using namespace Windows::System::UserProfile;
|
|
using namespace Windows::UI::Text;
|
|
using namespace Windows::UI::Xaml;
|
|
using namespace Windows::UI::Xaml::Controls;
|
|
using namespace Windows::UI::Xaml::Controls::Primitives;
|
|
using namespace Windows::UI::Xaml::Documents;
|
|
using namespace Windows::UI::Xaml::Media;
|
|
|
|
DEPENDENCY_PROPERTY_INITIALIZATION(LocalizationService, FontType);
|
|
DEPENDENCY_PROPERTY_INITIALIZATION(LocalizationService, FontSize);
|
|
|
|
static reader_writer_lock s_locServiceInstanceLock;
|
|
|
|
LocalizationService^ LocalizationService::s_singletonInstance = nullptr;
|
|
|
|
// Resources for the engine use numbers as keys. It's inconvenient, but also difficult to
|
|
// change given that the engine heavily relies on perfect ordering of certain elements.
|
|
// The key for open parenthesis, '(', is "48".
|
|
static constexpr auto s_openParenResourceKey = L"48";
|
|
|
|
LocalizationService^ LocalizationService::GetInstance()
|
|
{
|
|
|
|
if (s_singletonInstance == nullptr)
|
|
{
|
|
// Writer lock for the static maps
|
|
reader_writer_lock::scoped_lock lock(s_locServiceInstanceLock);
|
|
|
|
if (s_singletonInstance == nullptr)
|
|
{
|
|
s_singletonInstance = ref new LocalizationService();
|
|
}
|
|
}
|
|
return s_singletonInstance;
|
|
}
|
|
|
|
LocalizationService::LocalizationService()
|
|
{
|
|
m_language = ApplicationLanguages::Languages->GetAt(0);
|
|
m_flowDirection = ResourceContext::GetForCurrentView()->QualifierValues->Lookup(L"LayoutDirection")
|
|
!= L"LTR" ? FlowDirection::RightToLeft : FlowDirection::LeftToRight;
|
|
|
|
auto resourceLoader = AppResourceProvider::GetInstance();
|
|
m_fontFamilyOverride = resourceLoader.GetResourceString(L"LocalizedFontFamilyOverride");
|
|
|
|
String^ reserved = L"RESERVED_FOR_FONTLOC";
|
|
|
|
m_overrideFontApiValues = ((m_fontFamilyOverride != nullptr) && (m_fontFamilyOverride != reserved));
|
|
if (m_overrideFontApiValues)
|
|
{
|
|
String^ localizedUICaptionFontSizeFactorOverride = resourceLoader.GetResourceString(L"LocalizedUICaptionFontSizeFactorOverride");
|
|
String^ localizedUITextFontSizeFactorOverride = resourceLoader.GetResourceString(L"LocalizedUITextFontSizeFactorOverride");
|
|
String^ localizedFontWeightOverride = resourceLoader.GetResourceString(L"LocalizedFontWeightOverride");
|
|
|
|
// If any of the font overrides are modified then all of them need to be modified
|
|
assert(localizedFontWeightOverride != reserved);
|
|
assert(localizedUITextFontSizeFactorOverride != reserved);
|
|
assert(localizedUICaptionFontSizeFactorOverride != reserved);
|
|
|
|
m_fontWeightOverride = ParseFontWeight(localizedFontWeightOverride);
|
|
m_uiTextFontScaleFactorOverride = _wtof(localizedUITextFontSizeFactorOverride->Data());
|
|
m_uiCaptionFontScaleFactorOverride = _wtof(localizedUICaptionFontSizeFactorOverride->Data());
|
|
}
|
|
|
|
m_fontGroup = ref new LanguageFontGroup(m_language);
|
|
}
|
|
|
|
FontWeight LocalizationService::ParseFontWeight(String^ fontWeight)
|
|
{
|
|
wstring weight = fontWeight->Data();
|
|
transform(weight.begin(), weight.end(), weight.begin(), towlower);
|
|
fontWeight = ref new String(weight.c_str());
|
|
|
|
if (fontWeight == "black")
|
|
{
|
|
return FontWeights::Black;
|
|
}
|
|
else if (fontWeight == "bold")
|
|
{
|
|
return FontWeights::Bold;
|
|
}
|
|
else if (fontWeight == "extrablack")
|
|
{
|
|
return FontWeights::ExtraBlack;
|
|
}
|
|
else if (fontWeight == "extrabold")
|
|
{
|
|
return FontWeights::ExtraBold;
|
|
}
|
|
else if (fontWeight == "extralight")
|
|
{
|
|
return FontWeights::ExtraLight;
|
|
}
|
|
else if (fontWeight == "light")
|
|
{
|
|
return FontWeights::Light;
|
|
}
|
|
else if (fontWeight == "medium")
|
|
{
|
|
return FontWeights::Medium;
|
|
}
|
|
else if (fontWeight == "normal")
|
|
{
|
|
return FontWeights::Normal;
|
|
}
|
|
else if (fontWeight == "semibold")
|
|
{
|
|
return FontWeights::SemiBold;
|
|
}
|
|
else if (fontWeight == "semilight")
|
|
{
|
|
return FontWeights::SemiLight;
|
|
}
|
|
else if (fontWeight == "thin")
|
|
{
|
|
return FontWeights::Thin;
|
|
}
|
|
else
|
|
{
|
|
throw invalid_argument("Invalid argument: fontWeight");
|
|
}
|
|
}
|
|
|
|
FlowDirection LocalizationService::GetFlowDirection()
|
|
{
|
|
return m_flowDirection;
|
|
}
|
|
|
|
bool LocalizationService::IsRtlLayout()
|
|
{
|
|
return m_flowDirection == FlowDirection::RightToLeft;
|
|
}
|
|
|
|
String^ LocalizationService::GetLanguage()
|
|
{
|
|
return m_language;
|
|
}
|
|
|
|
bool LocalizationService::GetOverrideFontApiValues()
|
|
{
|
|
return m_overrideFontApiValues;
|
|
}
|
|
|
|
FontFamily^ LocalizationService::GetLanguageFontFamilyForType(LanguageFontType fontType)
|
|
{
|
|
if (m_overrideFontApiValues)
|
|
{
|
|
return ref new FontFamily(m_fontFamilyOverride);
|
|
}
|
|
else
|
|
{
|
|
return ref new FontFamily(GetLanguageFont(fontType)->FontFamily);
|
|
}
|
|
}
|
|
|
|
LanguageFont^ LocalizationService::GetLanguageFont(LanguageFontType fontType)
|
|
{
|
|
assert(!m_overrideFontApiValues);
|
|
assert(m_fontGroup);
|
|
|
|
switch (fontType)
|
|
{
|
|
case LanguageFontType::UIText:
|
|
return m_fontGroup->UITextFont;
|
|
case LanguageFontType::UICaption:
|
|
return m_fontGroup->UICaptionFont;
|
|
default:
|
|
throw std::invalid_argument("fontType");
|
|
}
|
|
}
|
|
|
|
String^ LocalizationService::GetFontFamilyOverride()
|
|
{
|
|
assert(m_overrideFontApiValues);
|
|
return m_fontFamilyOverride;
|
|
}
|
|
|
|
FontWeight LocalizationService::GetFontWeightOverride()
|
|
{
|
|
assert(m_overrideFontApiValues);
|
|
return m_fontWeightOverride;
|
|
}
|
|
|
|
double LocalizationService::GetFontScaleFactorOverride(LanguageFontType fontType)
|
|
{
|
|
assert(m_overrideFontApiValues);
|
|
|
|
switch (fontType)
|
|
{
|
|
case LanguageFontType::UIText:
|
|
return m_uiTextFontScaleFactorOverride;
|
|
case LanguageFontType::UICaption:
|
|
return m_uiCaptionFontScaleFactorOverride;
|
|
default:
|
|
throw invalid_argument("Invalid argument: fontType");
|
|
}
|
|
}
|
|
|
|
void LocalizationService::OnFontTypePropertyChanged(DependencyObject^ target, LanguageFontType /*oldValue*/, LanguageFontType /*newValue*/)
|
|
{
|
|
UpdateFontFamilyAndSize(target);
|
|
}
|
|
|
|
void LocalizationService::OnFontWeightPropertyChanged(DependencyObject^ target, FontWeight /*oldValue*/, FontWeight /*newValue*/)
|
|
{
|
|
UpdateFontFamilyAndSize(target);
|
|
}
|
|
|
|
void LocalizationService::OnFontSizePropertyChanged(DependencyObject^ target, double /*oldValue*/, double /*newValue*/)
|
|
{
|
|
UpdateFontFamilyAndSize(target);
|
|
}
|
|
|
|
void LocalizationService::UpdateFontFamilyAndSize(DependencyObject^ target)
|
|
{
|
|
FontFamily^ fontFamily;
|
|
FontWeight fontWeight;
|
|
bool fOverrideFontWeight = false;
|
|
double scaleFactor;
|
|
|
|
auto service = LocalizationService::GetInstance();
|
|
auto fontType = LocalizationService::GetFontType(target);
|
|
|
|
if (service->GetOverrideFontApiValues())
|
|
{
|
|
fontFamily = ref new FontFamily(service->GetFontFamilyOverride());
|
|
scaleFactor = service->GetFontScaleFactorOverride(fontType) / 100.0;
|
|
fontWeight = service->GetFontWeightOverride();
|
|
fOverrideFontWeight = true;
|
|
}
|
|
else
|
|
{
|
|
auto languageFont = service->GetLanguageFont(fontType);
|
|
fontFamily = ref new FontFamily(languageFont->FontFamily);
|
|
scaleFactor = languageFont->ScaleFactor / 100.0;
|
|
}
|
|
|
|
double sizeToUse = LocalizationService::GetFontSize(target) * scaleFactor;
|
|
|
|
auto control = dynamic_cast<Control^>(target);
|
|
if (control)
|
|
{
|
|
control->FontFamily = fontFamily;
|
|
if (fOverrideFontWeight)
|
|
{
|
|
control->FontWeight = fontWeight;
|
|
}
|
|
if (sizeToUse != 0.0)
|
|
{
|
|
control->FontSize = sizeToUse;
|
|
}
|
|
else
|
|
{
|
|
control->ClearValue(Control::FontSizeProperty);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto textBlock = dynamic_cast<TextBlock^>(target);
|
|
if (textBlock)
|
|
{
|
|
textBlock->FontFamily = fontFamily;
|
|
if (fOverrideFontWeight)
|
|
{
|
|
textBlock->FontWeight = fontWeight;
|
|
}
|
|
if (sizeToUse != 0.0)
|
|
{
|
|
textBlock->FontSize = sizeToUse;
|
|
}
|
|
else
|
|
{
|
|
textBlock->ClearValue(TextBlock::FontSizeProperty);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RichTextBlock^ richTextBlock = dynamic_cast<RichTextBlock^>(target);
|
|
if (richTextBlock)
|
|
{
|
|
richTextBlock->FontFamily = fontFamily;
|
|
if (fOverrideFontWeight)
|
|
{
|
|
richTextBlock->FontWeight = fontWeight;
|
|
}
|
|
if (sizeToUse != 0.0)
|
|
{
|
|
richTextBlock->FontSize = sizeToUse;
|
|
}
|
|
else
|
|
{
|
|
richTextBlock->ClearValue(RichTextBlock::FontSizeProperty);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TextElement^ textElement = dynamic_cast<TextElement^>(target);
|
|
if (textElement)
|
|
{
|
|
textElement->FontFamily = fontFamily;
|
|
if (fOverrideFontWeight)
|
|
{
|
|
textElement->FontWeight = fontWeight;
|
|
}
|
|
if (sizeToUse != 0.0)
|
|
{
|
|
textElement->FontSize = sizeToUse;
|
|
}
|
|
else
|
|
{
|
|
textElement->ClearValue(TextElement::FontSizeProperty);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If successful, returns a formatter that respects the user's regional format settings,
|
|
// as configured by running intl.cpl.
|
|
DecimalFormatter^ LocalizationService::GetRegionalSettingsAwareDecimalFormatter()
|
|
{
|
|
IIterable<String^>^ languageIdentifiers = LocalizationService::GetLanguageIdentifiers();
|
|
if (languageIdentifiers != nullptr)
|
|
{
|
|
return ref new DecimalFormatter(languageIdentifiers, GlobalizationPreferences::HomeGeographicRegion);
|
|
}
|
|
|
|
return ref new DecimalFormatter();
|
|
}
|
|
|
|
// If successful, returns a formatter that respects the user's regional format settings,
|
|
// as configured by running intl.cpl.
|
|
//
|
|
// This helper function creates a DateTimeFormatter with a TwentyFour hour clock
|
|
DateTimeFormatter^ LocalizationService::GetRegionalSettingsAwareDateTimeFormatter(_In_ String^ format)
|
|
{
|
|
IIterable<String^>^ languageIdentifiers = LocalizationService::GetLanguageIdentifiers();
|
|
if (languageIdentifiers == nullptr)
|
|
{
|
|
languageIdentifiers = ApplicationLanguages::Languages;
|
|
}
|
|
|
|
return ref new DateTimeFormatter(format, languageIdentifiers);
|
|
}
|
|
|
|
// If successful, returns a formatter that respects the user's regional format settings,
|
|
// as configured by running intl.cpl.
|
|
DateTimeFormatter^ LocalizationService::GetRegionalSettingsAwareDateTimeFormatter(
|
|
_In_ String^ format,
|
|
_In_ String^ calendarIdentifier,
|
|
_In_ String^ clockIdentifier)
|
|
{
|
|
IIterable<String^>^ languageIdentifiers = LocalizationService::GetLanguageIdentifiers();
|
|
if (languageIdentifiers == nullptr)
|
|
{
|
|
languageIdentifiers = ApplicationLanguages::Languages;
|
|
}
|
|
|
|
return ref new DateTimeFormatter(
|
|
format,
|
|
languageIdentifiers,
|
|
GlobalizationPreferences::HomeGeographicRegion,
|
|
calendarIdentifier,
|
|
clockIdentifier);
|
|
}
|
|
|
|
CurrencyFormatter^ LocalizationService::GetRegionalSettingsAwareCurrencyFormatter()
|
|
{
|
|
String^ userCurrency = (GlobalizationPreferences::Currencies->Size > 0)
|
|
? GlobalizationPreferences::Currencies->GetAt(0)
|
|
: StringReference(DefaultCurrencyCode.data());
|
|
|
|
IIterable<String^>^ languageIdentifiers = LocalizationService::GetLanguageIdentifiers();
|
|
if (languageIdentifiers == nullptr)
|
|
{
|
|
languageIdentifiers = ApplicationLanguages::Languages;
|
|
}
|
|
|
|
auto currencyFormatter = ref new CurrencyFormatter(
|
|
userCurrency,
|
|
languageIdentifiers,
|
|
GlobalizationPreferences::HomeGeographicRegion);
|
|
|
|
int fractionDigits = LocalizationSettings::GetInstance().GetCurrencyTrailingDigits();
|
|
currencyFormatter->FractionDigits = fractionDigits;
|
|
|
|
return currencyFormatter;
|
|
}
|
|
|
|
IIterable<String^>^ LocalizationService::GetLanguageIdentifiers()
|
|
{
|
|
WCHAR currentLocale[LOCALE_NAME_MAX_LENGTH] = {};
|
|
int result = GetUserDefaultLocaleName(currentLocale, LOCALE_NAME_MAX_LENGTH);
|
|
if (result != 0)
|
|
{
|
|
// GetUserDefaultLocaleName may return an invalid bcp47 language tag with trailing non-BCP47 friendly characters,
|
|
// which if present would start with an underscore, for example sort order
|
|
// (see https://msdn.microsoft.com/en-us/library/windows/desktop/dd373814(v=vs.85).aspx).
|
|
// Therefore, if there is an underscore in the locale name, trim all characters from the underscore onwards.
|
|
WCHAR* underscore = wcschr(currentLocale, L'_');
|
|
if (underscore != nullptr)
|
|
{
|
|
*underscore = L'\0';
|
|
}
|
|
|
|
String^ localeString = ref new String(currentLocale);
|
|
// validate if the locale we have is valid
|
|
// otherwise we fallback to the default.
|
|
if (Language::IsWellFormed(localeString))
|
|
{
|
|
auto languageList = ref new Vector<String^>();
|
|
languageList->Append(localeString);
|
|
return languageList;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
unordered_map<wstring, wstring> LocalizationService::GetTokenToReadableNameMap()
|
|
{
|
|
// Resources for the engine use numbers as keys. It's inconvenient, but also difficult to
|
|
// change given that the engine heavily relies on perfect ordering of certain elements.
|
|
// To compromise, we'll declare a map from engine resource key to automation name from the
|
|
// standard project resources.
|
|
static vector<pair<wstring, wstring>> s_parenEngineKeyResourceMap = {
|
|
// Sine permutations
|
|
make_pair<wstring, wstring>(L"67", L"SineDegrees"),
|
|
make_pair<wstring, wstring>(L"73", L"SineRadians"),
|
|
make_pair<wstring, wstring>(L"79", L"SineGradians"),
|
|
make_pair<wstring, wstring>(L"70", L"InverseSineDegrees"),
|
|
make_pair<wstring, wstring>(L"76", L"InverseSineRadians"),
|
|
make_pair<wstring, wstring>(L"82", L"InverseSineGradians"),
|
|
make_pair<wstring, wstring>(L"25", L"HyperbolicSine"),
|
|
make_pair<wstring, wstring>(L"85", L"InverseHyperbolicSine"),
|
|
|
|
// Cosine permutations
|
|
make_pair<wstring, wstring>(L"68", L"CosineDegrees"),
|
|
make_pair<wstring, wstring>(L"74", L"CosineRadians"),
|
|
make_pair<wstring, wstring>(L"80", L"CosineGradians"),
|
|
make_pair<wstring, wstring>(L"71", L"InverseCosineDegrees"),
|
|
make_pair<wstring, wstring>(L"77", L"InverseCosineRadians"),
|
|
make_pair<wstring, wstring>(L"83", L"InverseCosineGradians"),
|
|
make_pair<wstring, wstring>(L"26", L"HyperbolicCosine"),
|
|
make_pair<wstring, wstring>(L"86", L"InverseHyperbolicCosine"),
|
|
|
|
// Tangent permutations
|
|
make_pair<wstring, wstring>(L"69", L"TangentDegrees"),
|
|
make_pair<wstring, wstring>(L"75", L"TangentRadians"),
|
|
make_pair<wstring, wstring>(L"81", L"TangentGradians"),
|
|
make_pair<wstring, wstring>(L"72", L"InverseTangentDegrees"),
|
|
make_pair<wstring, wstring>(L"78", L"InverseTangentRadians"),
|
|
make_pair<wstring, wstring>(L"84", L"InverseTangentGradians"),
|
|
make_pair<wstring, wstring>(L"27", L"HyperbolicTangent"),
|
|
make_pair<wstring, wstring>(L"87", L"InverseHyperbolicTangent"),
|
|
|
|
// Miscellaneous Scientific functions
|
|
make_pair<wstring, wstring>(L"94", L"Factorial"),
|
|
make_pair<wstring, wstring>(L"35", L"DegreeMinuteSecond"),
|
|
make_pair<wstring, wstring>(L"28", L"NaturalLog"),
|
|
make_pair<wstring, wstring>(L"91", L"Square")
|
|
};
|
|
|
|
static vector<pair<wstring, wstring>> s_noParenEngineKeyResourceMap = {
|
|
// Programmer mode functions
|
|
make_pair<wstring, wstring>(L"9", L"LeftShift"),
|
|
make_pair<wstring, wstring>(L"10", L"RightShift"),
|
|
|
|
// Y Root scientific function
|
|
make_pair<wstring, wstring>(L"16", L"YRoot")
|
|
};
|
|
|
|
unordered_map<wstring, wstring> tokenToReadableNameMap{};
|
|
auto resProvider = AppResourceProvider::GetInstance();
|
|
|
|
static const wstring openParen = resProvider.GetCEngineString(StringReference(s_openParenResourceKey))->Data();
|
|
|
|
for (const auto& keyPair : s_parenEngineKeyResourceMap)
|
|
{
|
|
wstring engineStr = resProvider.GetCEngineString(StringReference(keyPair.first.c_str()))->Data();
|
|
wstring automationName = resProvider.GetResourceString(StringReference(keyPair.second.c_str()))->Data();
|
|
|
|
tokenToReadableNameMap.emplace(engineStr + openParen, automationName);
|
|
}
|
|
s_parenEngineKeyResourceMap.clear();
|
|
|
|
for (const auto& keyPair : s_noParenEngineKeyResourceMap)
|
|
{
|
|
wstring engineStr = resProvider.GetCEngineString(StringReference(keyPair.first.c_str()))->Data();
|
|
wstring automationName = resProvider.GetResourceString(StringReference(keyPair.second.c_str()))->Data();
|
|
|
|
tokenToReadableNameMap.emplace(engineStr, automationName);
|
|
}
|
|
s_noParenEngineKeyResourceMap.clear();
|
|
|
|
// Also replace hyphens with "minus"
|
|
wstring minusText = resProvider.GetResourceString(L"minus")->Data();
|
|
tokenToReadableNameMap.emplace(L"-", minusText);
|
|
|
|
return tokenToReadableNameMap;
|
|
}
|
|
|
|
String^ LocalizationService::GetNarratorReadableToken(String^ rawToken)
|
|
{
|
|
static unordered_map<wstring, wstring> s_tokenToReadableNameMap = GetTokenToReadableNameMap();
|
|
|
|
auto itr = s_tokenToReadableNameMap.find(rawToken->Data());
|
|
if (itr == s_tokenToReadableNameMap.end())
|
|
{
|
|
return rawToken;
|
|
}
|
|
else
|
|
{
|
|
static const String^ openParen = AppResourceProvider::GetInstance().GetCEngineString(StringReference(s_openParenResourceKey));
|
|
return ref new String(itr->second.c_str()) + L" " + openParen;
|
|
}
|
|
}
|
|
|
|
String^ LocalizationService::GetNarratorReadableString(String^ rawString)
|
|
{
|
|
wstringstream readableString{};
|
|
readableString << L"";
|
|
|
|
wstring asWstring = rawString->Data();
|
|
for (const auto& c : asWstring)
|
|
{
|
|
readableString << LocalizationService::GetNarratorReadableToken(L"" + c)->Data();
|
|
}
|
|
|
|
return ref new String(readableString.str().c_str());
|
|
}
|