Skip to content

Commit 9d6d6be

Browse files
authored
Work around a currency service defect for localization (#2302)
* always use `lang-region` for localization * language code may have a single segment
1 parent 758c550 commit 9d6d6be

File tree

1 file changed

+103
-49
lines changed

1 file changed

+103
-49
lines changed

src/CalcViewModel/DataLoaders/CurrencyDataLoader.cpp

+103-49
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the MIT License.
33

44
#include "pch.h"
5+
#include <optional>
6+
57
#include "CurrencyDataLoader.h"
68
#include "Common/AppResourceProvider.h"
79
#include "Common/LocalizationStringUtil.h"
@@ -31,41 +33,104 @@ using namespace Windows::System::UserProfile;
3133
using namespace Windows::UI::Core;
3234
using namespace Windows::Web::Http;
3335

34-
static constexpr auto CURRENCY_UNIT_FROM_KEY = L"CURRENCY_UNIT_FROM_KEY";
35-
static constexpr auto CURRENCY_UNIT_TO_KEY = L"CURRENCY_UNIT_TO_KEY";
36-
37-
// Calculate number of 100-nanosecond intervals-per-day
38-
// (1 interval/100 nanosecond)(100 nanosecond/1e-7 s)(60 s/1 min)(60 min/1 hr)(24 hr/1 day) = (interval/day)
39-
static constexpr long long DAY_DURATION = 1LL * 60 * 60 * 24 * 10000000;
40-
static constexpr long long WEEK_DURATION = DAY_DURATION * 7;
41-
42-
static constexpr int FORMATTER_RATE_FRACTION_PADDING = 2;
43-
static constexpr int FORMATTER_RATE_MIN_DECIMALS = 4;
44-
static constexpr int FORMATTER_RATE_MIN_SIGNIFICANT_DECIMALS = 4;
45-
46-
static constexpr auto CACHE_TIMESTAMP_KEY = L"CURRENCY_CONVERTER_TIMESTAMP";
47-
static constexpr auto CACHE_LANGCODE_KEY = L"CURRENCY_CONVERTER_LANGCODE";
48-
static constexpr auto CACHE_DELIMITER = L"%";
49-
50-
static constexpr auto STATIC_DATA_FILENAME = L"CURRENCY_CONVERTER_STATIC_DATA.txt";
51-
static constexpr array<wstring_view, 5> STATIC_DATA_PROPERTIES = { wstring_view{ L"CountryCode", 11 },
52-
wstring_view{ L"CountryName", 11 },
53-
wstring_view{ L"CurrencyCode", 12 },
54-
wstring_view{ L"CurrencyName", 12 },
55-
wstring_view{ L"CurrencySymbol", 14 } };
56-
57-
static constexpr auto ALL_RATIOS_DATA_FILENAME = L"CURRENCY_CONVERTER_ALL_RATIOS_DATA.txt";
58-
static constexpr auto RATIO_KEY = L"Rt";
59-
static constexpr auto CURRENCY_CODE_KEY = L"An";
60-
static constexpr array<wstring_view, 2> ALL_RATIOS_DATA_PROPERTIES = { wstring_view{ RATIO_KEY, 2 }, wstring_view{ CURRENCY_CODE_KEY, 2 } };
61-
62-
static constexpr auto DEFAULT_FROM_TO_CURRENCY_FILE_URI = L"ms-appx:///DataLoaders/DefaultFromToCurrency.json";
63-
static constexpr auto FROM_KEY = L"from";
64-
static constexpr auto TO_KEY = L"to";
36+
namespace
37+
{
38+
constexpr auto CURRENCY_UNIT_FROM_KEY = L"CURRENCY_UNIT_FROM_KEY";
39+
constexpr auto CURRENCY_UNIT_TO_KEY = L"CURRENCY_UNIT_TO_KEY";
40+
41+
// Calculate number of 100-nanosecond intervals-per-day
42+
// (1 interval/100 nanosecond)(100 nanosecond/1e-7 s)(60 s/1 min)(60 min/1 hr)(24 hr/1 day) = (interval/day)
43+
constexpr long long DAY_DURATION = 1LL * 60 * 60 * 24 * 10000000;
44+
constexpr long long WEEK_DURATION = DAY_DURATION * 7;
45+
46+
constexpr int FORMATTER_RATE_FRACTION_PADDING = 2;
47+
constexpr int FORMATTER_RATE_MIN_DECIMALS = 4;
48+
constexpr int FORMATTER_RATE_MIN_SIGNIFICANT_DECIMALS = 4;
49+
50+
constexpr auto CACHE_TIMESTAMP_KEY = L"CURRENCY_CONVERTER_TIMESTAMP";
51+
constexpr auto CACHE_LANGCODE_KEY = L"CURRENCY_CONVERTER_LANGCODE";
52+
constexpr auto CACHE_DELIMITER = L"%";
53+
54+
constexpr auto STATIC_DATA_FILENAME = L"CURRENCY_CONVERTER_STATIC_DATA.txt";
55+
constexpr array<wstring_view, 5> STATIC_DATA_PROPERTIES = { wstring_view{ L"CountryCode", 11 },
56+
wstring_view{ L"CountryName", 11 },
57+
wstring_view{ L"CurrencyCode", 12 },
58+
wstring_view{ L"CurrencyName", 12 },
59+
wstring_view{ L"CurrencySymbol", 14 } };
60+
61+
constexpr auto ALL_RATIOS_DATA_FILENAME = L"CURRENCY_CONVERTER_ALL_RATIOS_DATA.txt";
62+
constexpr auto RATIO_KEY = L"Rt";
63+
constexpr auto CURRENCY_CODE_KEY = L"An";
64+
constexpr array<wstring_view, 2> ALL_RATIOS_DATA_PROPERTIES = { wstring_view{ RATIO_KEY, 2 }, wstring_view{ CURRENCY_CODE_KEY, 2 } };
65+
66+
constexpr auto DEFAULT_FROM_TO_CURRENCY_FILE_URI = L"ms-appx:///DataLoaders/DefaultFromToCurrency.json";
67+
constexpr auto FROM_KEY = L"from";
68+
constexpr auto TO_KEY = L"to";
69+
70+
// Fallback default values.
71+
constexpr auto DEFAULT_FROM_CURRENCY = DefaultCurrencyCode.data();
72+
constexpr auto DEFAULT_TO_CURRENCY = L"EUR";
73+
74+
// ParseLanguageCode returns language code in form of `lang-region`
75+
// TODO: unit testing.
76+
std::optional<std::wstring> ParseLanguageCode(const wchar_t* bcp47)
77+
{
78+
// the IETF BCP 47 language tag syntax is: language[-script][-region]...
79+
std::vector<std::wstring> segments;
80+
std::wstring cur;
81+
// TODO: use C++20 - ranges::views::split_view in the future.
82+
for (; *bcp47 != L'\0' && segments.size() < 3; ++bcp47)
83+
{
84+
auto ch = *bcp47;
85+
if (std::isalpha(static_cast<unsigned char>(ch)))
86+
{
87+
cur += ch;
88+
}
89+
else if (ch == L'-')
90+
{
91+
segments.push_back(std::exchange(cur, {}));
92+
}
93+
else
94+
{
95+
return std::nullopt;
96+
}
97+
}
98+
if (!cur.empty())
99+
{
100+
segments.push_back(std::move(cur));
101+
}
65102

66-
// Fallback default values.
67-
static constexpr auto DEFAULT_FROM_CURRENCY = DefaultCurrencyCode.data();
68-
static constexpr auto DEFAULT_TO_CURRENCY = L"EUR";
103+
switch (segments.size())
104+
{
105+
case 1:
106+
return segments[0];
107+
case 2:
108+
if (segments[1].size() == 2)
109+
{ // segments[1] is a region subtag.
110+
return segments[0] + L"-" + segments[1];
111+
}
112+
else
113+
{
114+
return segments[0];
115+
}
116+
case 3:
117+
if (segments[1].size() == 2)
118+
{ // segments[1] is a region subtag.
119+
return segments[0] + L"-" + segments[1];
120+
}
121+
else if (segments[1].size() != 2 && segments[2].size() == 2)
122+
{ // segments[2] is a region subtag.
123+
return segments[0] + L"-" + segments[2];
124+
}
125+
else
126+
{
127+
return segments[0];
128+
}
129+
default:
130+
return std::nullopt;
131+
}
132+
}
133+
} // namespace
69134

70135
namespace CalculatorApp
71136
{
@@ -99,25 +164,14 @@ CurrencyDataLoader::CurrencyDataLoader(const wchar_t* forcedResponseLanguage)
99164
{
100165
if (forcedResponseLanguage != nullptr)
101166
{
167+
assert(wcslen(forcedResponseLanguage) > 0 && "forcedResponseLanguage shall not be empty.");
102168
m_responseLanguage = ref new Platform::String(forcedResponseLanguage);
103169
}
104-
else
170+
else if (GlobalizationPreferences::Languages->Size > 0)
105171
{
106-
if (GlobalizationPreferences::Languages->Size > 0)
107-
{
108-
m_responseLanguage = GlobalizationPreferences::Languages->GetAt(0);
109-
110-
// Workaround for Simplified Chinese localization issue of currency API.
111-
std::wstring_view responseLanguage(m_responseLanguage->Data(), m_responseLanguage->Length());
112-
std::match_results<std::wstring_view::const_iterator> match;
113-
if (std::regex_match(responseLanguage.cbegin(), responseLanguage.cend(), match, std::wregex(L"zh-hans-[a-z]+", std::regex_constants::icase)))
114-
{
115-
m_responseLanguage = L"zh-CN";
116-
}
117-
}
118-
else
172+
if (auto lang = ParseLanguageCode(GlobalizationPreferences::Languages->GetAt(0)->Data()); lang.has_value())
119173
{
120-
m_responseLanguage = L"en-US";
174+
m_responseLanguage = ref new Platform::String{ lang->c_str() };
121175
}
122176
}
123177

0 commit comments

Comments
 (0)