|
| 1 | +// Copyright 2019 The Flutter Authors. All rights reserved. |
| 2 | +// Use of this source code is governed by a BSD-style license that can be |
| 3 | +// found in the LICENSE file. |
| 4 | + |
| 5 | +#include "fuchsia_intl.h" |
| 6 | + |
| 7 | +#include <sstream> |
| 8 | +#include <string> |
| 9 | +#include <vector> |
| 10 | + |
| 11 | +#include "loop.h" |
| 12 | +#include "rapidjson/document.h" |
| 13 | +#include "rapidjson/stringbuffer.h" |
| 14 | +#include "rapidjson/writer.h" |
| 15 | +#include "runner.h" |
| 16 | +#include "runtime/dart/utils/tempfs.h" |
| 17 | +#include "third_party/icu/source/common/unicode/bytestream.h" |
| 18 | +#include "third_party/icu/source/common/unicode/errorcode.h" |
| 19 | +#include "third_party/icu/source/common/unicode/locid.h" |
| 20 | +#include "third_party/icu/source/common/unicode/strenum.h" |
| 21 | +#include "third_party/icu/source/common/unicode/stringpiece.h" |
| 22 | +#include "third_party/icu/source/common/unicode/uloc.h" |
| 23 | + |
| 24 | +namespace { |
| 25 | + |
| 26 | +using icu::Locale; |
| 27 | + |
| 28 | +// This backfills for the static method icu::Locale::forLanguageTag which is |
| 29 | +// missing in ICU versions prior to 63. |
| 30 | +// |
| 31 | +// The implementation of the ICU version is here: |
| 32 | +// https://sourcegraph.com/github.com/unicode-org/icu@64b58ccda315902b7b88e32a12ffca3b7b99cdf0/-/blob/icu4c/source/common/unicode/locid.h#L408:29 |
| 33 | +// |
| 34 | +// This implementation differs in that it must use the C API to obtain the |
| 35 | +// locale, and that it initializes the default locale in the process. The C++ |
| 36 | +// version does not need to do either, but it achieves this by accessing the |
| 37 | +// private API for icu::Locale, which we can not duplicate. We are also using |
| 38 | +// the properties of the fuchsia.intl.ProfileProvider not to have to handle |
| 39 | +// certain edge cases. |
| 40 | +static icu::Locale forLanguageTag(const std::string& tag, UErrorCode& status) { |
| 41 | + // The ICU library has a special "bogus" locale, which we do not have. |
| 42 | + icu::Locale bogus; |
| 43 | + if (U_FAILURE(status)) { |
| 44 | + return bogus; |
| 45 | + } |
| 46 | + // This is for the "preflight" mode, in which we first guess at the buffer |
| 47 | + // size and parse; and retry the parse in case the buffer overflowed on the |
| 48 | + // first go, based on the size information returned from the preflight. |
| 49 | + const int32_t initial_size = 100; |
| 50 | + std::vector<char> buffer(initial_size); |
| 51 | + int32_t parsed_size = 0; |
| 52 | + uloc_forLanguageTag(tag.c_str(), buffer.data(), buffer.size(), &parsed_size, |
| 53 | + &status); |
| 54 | + if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) { |
| 55 | + // This is not recoverable, exit. |
| 56 | + return bogus; |
| 57 | + } |
| 58 | + buffer.resize(parsed_size); |
| 59 | + if (status == U_BUFFER_OVERFLOW_ERROR) { |
| 60 | + // If the locale was too long to fit into the buffer, since now we know |
| 61 | + // the parsed size and have resized the buffer, we can parse again. |
| 62 | + status = U_ZERO_ERROR; |
| 63 | + uloc_forLanguageTag(tag.c_str(), buffer.data(), buffer.size(), &parsed_size, |
| 64 | + &status); |
| 65 | + } |
| 66 | + if (U_FAILURE(status)) { |
| 67 | + // This time we can not recover, so exit. |
| 68 | + return bogus; |
| 69 | + } |
| 70 | + std::string locale_string(begin(buffer), end(buffer)); |
| 71 | + // Strip the POSIX @-suffix that we may get after resolving BCP-47 locale |
| 72 | + // IDs like "en-US-u-ca-gregory-fw-sun-...". |
| 73 | + const size_t locale_end = locale_string.find("@"); |
| 74 | + locale_string = locale_string.substr(0, locale_end); |
| 75 | + return icu::Locale::createCanonical(locale_string.c_str()); |
| 76 | +} |
| 77 | + |
| 78 | +} // namespace |
| 79 | + |
| 80 | +namespace flutter_runner { |
| 81 | + |
| 82 | +using fuchsia::intl::Profile; |
| 83 | + |
| 84 | +std::vector<uint8_t> MakeLocalizationPlatformMessageData( |
| 85 | + const Profile& intl_profile) { |
| 86 | + rapidjson::Document document; |
| 87 | + auto& allocator = document.GetAllocator(); |
| 88 | + document.SetObject(); |
| 89 | + document.AddMember("method", "setLocale", allocator); |
| 90 | + rapidjson::Value args(rapidjson::kArrayType); |
| 91 | + |
| 92 | + for (const auto& locale_id : intl_profile.locales()) { |
| 93 | + UErrorCode error_code = U_ZERO_ERROR; |
| 94 | + icu::Locale locale = forLanguageTag(locale_id.id, error_code); |
| 95 | + if (U_FAILURE(error_code)) { |
| 96 | + FML_LOG(ERROR) << "Error parsing locale ID \"" << locale_id.id << "\""; |
| 97 | + continue; |
| 98 | + } |
| 99 | + args.PushBack(rapidjson::Value().SetString(locale.getLanguage(), allocator), |
| 100 | + allocator); |
| 101 | + |
| 102 | + auto country = locale.getCountry() != nullptr ? locale.getCountry() : ""; |
| 103 | + args.PushBack(rapidjson::Value().SetString(country, allocator), allocator); |
| 104 | + |
| 105 | + auto script = locale.getScript() != nullptr ? locale.getScript() : ""; |
| 106 | + args.PushBack(rapidjson::Value().SetString(script, allocator), allocator); |
| 107 | + |
| 108 | + std::string variant = |
| 109 | + locale.getVariant() != nullptr ? locale.getVariant() : ""; |
| 110 | + // ICU4C capitalizes the variant for backward compatibility, even though |
| 111 | + // the preferred form is lowercase. So we lowercase here. |
| 112 | + std::transform(begin(variant), end(variant), begin(variant), |
| 113 | + [](unsigned char c) { return std::tolower(c); }); |
| 114 | + args.PushBack(rapidjson::Value().SetString(variant, allocator), allocator); |
| 115 | + } |
| 116 | + |
| 117 | + document.AddMember("args", args, allocator); |
| 118 | + |
| 119 | + rapidjson::StringBuffer buffer; |
| 120 | + rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); |
| 121 | + document.Accept(writer); |
| 122 | + auto data = reinterpret_cast<const uint8_t*>(buffer.GetString()); |
| 123 | + return std::vector<uint8_t>(data, data + buffer.GetSize()); |
| 124 | +} |
| 125 | + |
| 126 | +} // namespace flutter_runner |
0 commit comments