// 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(nullptr); } } return s_singletonInstance; } /// /// Replace (or create) the single instance of this singleton class by one with the language passed as parameter /// /// RFC-5646 identifier of the language to use /// /// Should only be used for test purpose /// void LocalizationService::OverrideWithLanguage(_In_ const wchar_t * const language) { s_singletonInstance = ref new LocalizationService(language); } /// /// Constructor /// /// RFC-5646 identifier of the language to use, if null, will use the current language of the system LocalizationService::LocalizationService(_In_ const wchar_t * const overridedLanguage) { m_isLanguageOverrided = overridedLanguage != nullptr; m_language = m_isLanguageOverrided ? ref new Platform::String(overridedLanguage) : ApplicationLanguages::Languages->GetAt(0); m_flowDirection = ResourceContext::GetForViewIndependentUse()->QualifierValues->Lookup(L"LayoutDirection") != L"LTR" ? FlowDirection::RightToLeft : FlowDirection::LeftToRight; wstring localeName = wstring(m_language->Data()); localeName += L".UTF8"; try { // Convert wstring to string for locale int size_needed = WideCharToMultiByte(CP_UTF8, 0, &localeName[0], (int)localeName.size(), NULL, 0, NULL, NULL); string localeNameStr(size_needed, 0); WideCharToMultiByte(CP_UTF8, 0, &localeName[0], (int)localeName.size(), &localeNameStr[0], size_needed, NULL, NULL); m_locale = locale(localeNameStr.data()); } catch (...) { m_locale = locale(""); } 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(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(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(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(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() const { IIterable ^ 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) const { IIterable ^ 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) const { IIterable ^ languageIdentifiers = LocalizationService::GetLanguageIdentifiers(); if (languageIdentifiers == nullptr) { languageIdentifiers = ApplicationLanguages::Languages; } return ref new DateTimeFormatter(format, languageIdentifiers, GlobalizationPreferences::HomeGeographicRegion, calendarIdentifier, clockIdentifier); } CurrencyFormatter ^ LocalizationService::GetRegionalSettingsAwareCurrencyFormatter() const { String ^ userCurrency = (GlobalizationPreferences::Currencies->Size > 0) ? GlobalizationPreferences::Currencies->GetAt(0) : StringReference(DefaultCurrencyCode.data()); IIterable ^ languageIdentifiers = 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 ^ LocalizationService::GetLanguageIdentifiers() const { WCHAR currentLocale[LOCALE_NAME_MAX_LENGTH] = {}; int result = GetUserDefaultLocaleName(currentLocale, LOCALE_NAME_MAX_LENGTH); if (m_isLanguageOverrided) { auto overridedLanguageList = ref new Vector(); overridedLanguageList->Append(m_language); return overridedLanguageList; } 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(); languageList->Append(localeString); return languageList; } } return nullptr; } unordered_map 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> s_parenEngineKeyResourceMap = { // Sine permutations make_pair(L"67", L"SineDegrees"), make_pair(L"73", L"SineRadians"), make_pair(L"79", L"SineGradians"), make_pair(L"70", L"InverseSineDegrees"), make_pair(L"76", L"InverseSineRadians"), make_pair(L"82", L"InverseSineGradians"), make_pair(L"25", L"HyperbolicSine"), make_pair(L"85", L"InverseHyperbolicSine"), // Cosine permutations make_pair(L"68", L"CosineDegrees"), make_pair(L"74", L"CosineRadians"), make_pair(L"80", L"CosineGradians"), make_pair(L"71", L"InverseCosineDegrees"), make_pair(L"77", L"InverseCosineRadians"), make_pair(L"83", L"InverseCosineGradians"), make_pair(L"26", L"HyperbolicCosine"), make_pair(L"86", L"InverseHyperbolicCosine"), // Tangent permutations make_pair(L"69", L"TangentDegrees"), make_pair(L"75", L"TangentRadians"), make_pair(L"81", L"TangentGradians"), make_pair(L"72", L"InverseTangentDegrees"), make_pair(L"78", L"InverseTangentRadians"), make_pair(L"84", L"InverseTangentGradians"), make_pair(L"27", L"HyperbolicTangent"), make_pair(L"87", L"InverseHyperbolicTangent"), // Secant permutations make_pair(L"SecDeg", L"SecantDegrees"), make_pair(L"SecRad", L"SecantRadians"), make_pair(L"SecGrad", L"SecantGradians"), make_pair(L"InverseSecDeg", L"InverseSecantDegrees"), make_pair(L"InverseSecRad", L"InverseSecantRadians"), make_pair(L"InverseSecGrad", L"InverseSecantGradians"), make_pair(L"Sech", L"HyperbolicSecant"), make_pair(L"InverseSech", L"InverseHyperbolicSecant"), // Cosecant permutations make_pair(L"CscDeg", L"CosecantDegrees"), make_pair(L"CscRad", L"CosecantRadians"), make_pair(L"CscGrad", L"CosecantGradians"), make_pair(L"InverseCscDeg", L"InverseCosecantDegrees"), make_pair(L"InverseCscRad", L"InverseCosecantRadians"), make_pair(L"InverseCscGrad", L"InverseCosecantGradians"), make_pair(L"Csch", L"HyperbolicCosecant"), make_pair(L"InverseCsch", L"InverseHyperbolicCosecant"), // Cotangent permutations make_pair(L"CotDeg", L"CotangentDegrees"), make_pair(L"CotRad", L"CotangentRadians"), make_pair(L"CotGrad", L"CotangentGradians"), make_pair(L"InverseCotDeg", L"InverseCotangentDegrees"), make_pair(L"InverseCotRad", L"InverseCotangentRadians"), make_pair(L"InverseCotGrad", L"InverseCotangentGradians"), make_pair(L"Coth", L"HyperbolicCotangent"), make_pair(L"InverseCoth", L"InverseHyperbolicCotangent"), // Miscellaneous Scientific functions make_pair(L"94", L"Factorial"), make_pair(L"35", L"DegreeMinuteSecond"), make_pair(L"28", L"NaturalLog"), make_pair(L"91", L"Square"), make_pair(L"CubeRoot", L"CubeRoot"), make_pair(L"Abs", L"AbsoluteValue") }; static vector> s_noParenEngineKeyResourceMap = { // Programmer mode functions make_pair(L"9", L"LeftShift"), make_pair(L"10", L"RightShift"), make_pair(L"LogBaseX", L"Logx"), // Y Root scientific function make_pair(L"16", L"YRoot") }; unordered_map 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 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) { wstring readableString{}; wstring asWstring = rawString->Data(); for (const auto& c : asWstring) { readableString += LocalizationService::GetNarratorReadableToken(ref new String(&c, 1))->Data(); } return ref new String(readableString.c_str()); } void LocalizationService::Sort(std::vector& source) { const collate& coll = use_facet>(m_locale); sort(source.begin(), source.end(), [&coll](Platform::String ^ str1, Platform::String ^ str2) { return coll.compare(str1->Begin(), str1->End(), str2->Begin(), str2->End()) < 0; }); }