From 8b581d8b638951f98c0fb0c0116ac18b355b825e Mon Sep 17 00:00:00 2001 From: jshin Date: Fri, 7 Aug 2015 03:11:09 -0700 Subject: [PATCH] Add ICU message format support Adopt and customize a ICU message format wrapper used at Google to meet Chromium's need. This will enable formatting of 'complex messages' requiring plural and/or selector (e.g. gender or 'single vs multiple') support with more than one parameters. Besides, l10n_util::GetPluralStringF* is rewritten to use this API. I'm also planning to use this API to add a similar support to Chromium's JavaScript-based UI and extensions. References: MessageFormat specs: http://icu-project.org/apiref/icu4j/com/ibm/icu/text/MessageFormat.html http://icu-project.org/apiref/icu4c/classicu_1_1DecimalFormat.html#details Examples: http://userguide.icu-project.org/formatparse/messages message_formatter_unittest.cc go/plurals inside Google. BUG=481734 TEST=base_unittests --gtest_filter="MessageFormat*" Review URL: https://codereview.chromium.org/1140153005 Cr-Commit-Position: refs/heads/master@{#342327} --- base/BUILD.gn | 7 +- base/base.gyp | 3 + base/base.gypi | 2 + base/i18n/message_formatter.cc | 141 ++++++++++++++++ base/i18n/message_formatter.h | 111 +++++++++++++ base/i18n/message_formatter_unittest.cc | 180 +++++++++++++++++++++ chrome/app/generated_resources.grd | 12 +- chrome/browser/ssl/ssl_error_info.cc | 19 +-- extensions/browser/BUILD.gn | 1 + .../browser/api/device_permissions_prompt.cc | 18 +-- extensions/extensions.gyp | 1 + extensions/extensions_strings.grd | 32 ++-- ui/base/BUILD.gn | 2 - ui/base/l10n/formatter.cc | 43 ++++- ui/base/l10n/l10n_util.cc | 23 ++- ui/base/l10n/l10n_util.h | 7 + ui/base/l10n/l10n_util_plurals.cc | 37 ----- ui/base/l10n/l10n_util_plurals.h | 32 ---- ui/base/ui_base.gyp | 2 - 19 files changed, 535 insertions(+), 138 deletions(-) create mode 100644 base/i18n/message_formatter.cc create mode 100644 base/i18n/message_formatter.h create mode 100644 base/i18n/message_formatter_unittest.cc delete mode 100644 ui/base/l10n/l10n_util_plurals.cc delete mode 100644 ui/base/l10n/l10n_util_plurals.h diff --git a/base/BUILD.gn b/base/BUILD.gn index b8ae3a9e5c39..6f364a95bdfc 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn @@ -987,6 +987,8 @@ component("i18n") { "i18n/icu_string_conversions.h", "i18n/icu_util.cc", "i18n/icu_util.h", + "i18n/message_formatter.cc", + "i18n/message_formatter.h", "i18n/number_formatting.cc", "i18n/number_formatting.h", "i18n/rtl.cc", @@ -1006,10 +1008,12 @@ component("i18n") { ] defines = [ "BASE_I18N_IMPLEMENTATION" ] configs += [ "//build/config/compiler:wexit_time_destructors" ] + public_deps = [ + "//third_party/icu", + ] deps = [ ":base", "//base/third_party/dynamic_annotations", - "//third_party/icu", ] if (!is_debug) { @@ -1261,6 +1265,7 @@ test("base_unittests") { "i18n/char_iterator_unittest.cc", "i18n/file_util_icu_unittest.cc", "i18n/icu_string_conversions_unittest.cc", + "i18n/message_formatter_unittest.cc", "i18n/number_formatting_unittest.cc", "i18n/rtl_unittest.cc", "i18n/streaming_utf8_validator_unittest.cc", diff --git a/base/base.gyp b/base/base.gyp index e9b09959da83..4a558d8db5cf 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -288,6 +288,8 @@ ], 'export_dependent_settings': [ 'base', + '../third_party/icu/icu.gyp:icuuc', + '../third_party/icu/icu.gyp:icui18n', ], 'includes': [ '../build/android/increase_size_for_speed.gypi', @@ -483,6 +485,7 @@ 'i18n/char_iterator_unittest.cc', 'i18n/file_util_icu_unittest.cc', 'i18n/icu_string_conversions_unittest.cc', + 'i18n/message_formatter_unittest.cc', 'i18n/number_formatting_unittest.cc', 'i18n/rtl_unittest.cc', 'i18n/streaming_utf8_validator_unittest.cc', diff --git a/base/base.gypi b/base/base.gypi index c3725a035113..cce8e4491a87 100644 --- a/base/base.gypi +++ b/base/base.gypi @@ -1018,6 +1018,8 @@ 'i18n/icu_string_conversions.h', 'i18n/icu_util.cc', 'i18n/icu_util.h', + 'i18n/message_formatter.cc', + 'i18n/message_formatter.h', 'i18n/number_formatting.cc', 'i18n/number_formatting.h', 'i18n/rtl.cc', diff --git a/base/i18n/message_formatter.cc b/base/i18n/message_formatter.cc new file mode 100644 index 000000000000..702e51b94aaa --- /dev/null +++ b/base/i18n/message_formatter.cc @@ -0,0 +1,141 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/i18n/message_formatter.h" + +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "base/time/time.h" +#include "third_party/icu/source/common/unicode/unistr.h" +#include "third_party/icu/source/common/unicode/utypes.h" +#include "third_party/icu/source/i18n/unicode/fmtable.h" +#include "third_party/icu/source/i18n/unicode/msgfmt.h" + +using icu::UnicodeString; + +namespace base { +namespace i18n { +namespace { +UnicodeString UnicodeStringFromStringPiece(StringPiece str) { + return UnicodeString::fromUTF8( + icu::StringPiece(str.data(), base::checked_cast(str.size()))); +} +} // anonymous namespace + +namespace internal { +MessageArg::MessageArg() : formattable(nullptr) {} + +MessageArg::MessageArg(const char* s) + : formattable(new icu::Formattable(UnicodeStringFromStringPiece(s))) {} + +MessageArg::MessageArg(StringPiece s) + : formattable(new icu::Formattable(UnicodeStringFromStringPiece(s))) {} + +MessageArg::MessageArg(const std::string& s) + : formattable(new icu::Formattable(UnicodeString::fromUTF8(s))) {} + +MessageArg::MessageArg(const string16& s) + : formattable(new icu::Formattable(UnicodeString(s.data(), s.size()))) {} + +MessageArg::MessageArg(int i) : formattable(new icu::Formattable(i)) {} + +MessageArg::MessageArg(int64_t i) : formattable(new icu::Formattable(i)) {} + +MessageArg::MessageArg(double d) : formattable(new icu::Formattable(d)) {} + +MessageArg::MessageArg(const Time& t) + : formattable(new icu::Formattable(static_cast(t.ToJsTime()))) {} + +MessageArg::~MessageArg() {} + +// Tests if this argument has a value, and if so increments *count. +bool MessageArg::has_value(int *count) const { + if (formattable == nullptr) + return false; + + ++*count; + return true; +} + +} // namespace internal + +string16 MessageFormatter::FormatWithNumberedArgs( + StringPiece16 msg, + const internal::MessageArg& arg0, + const internal::MessageArg& arg1, + const internal::MessageArg& arg2, + const internal::MessageArg& arg3, + const internal::MessageArg& arg4, + const internal::MessageArg& arg5, + const internal::MessageArg& arg6) { + int32_t args_count = 0; + icu::Formattable args[] = { + arg0.has_value(&args_count) ? *arg0.formattable : icu::Formattable(), + arg1.has_value(&args_count) ? *arg1.formattable : icu::Formattable(), + arg2.has_value(&args_count) ? *arg2.formattable : icu::Formattable(), + arg3.has_value(&args_count) ? *arg3.formattable : icu::Formattable(), + arg4.has_value(&args_count) ? *arg4.formattable : icu::Formattable(), + arg5.has_value(&args_count) ? *arg5.formattable : icu::Formattable(), + arg6.has_value(&args_count) ? *arg6.formattable : icu::Formattable(), + }; + + UnicodeString msg_string(msg.data(), msg.size()); + UErrorCode error = U_ZERO_ERROR; + icu::MessageFormat format(msg_string, error); + icu::UnicodeString formatted; + icu::FieldPosition ignore(icu::FieldPosition::DONT_CARE); + format.format(args, args_count, formatted, ignore, error); + if (U_FAILURE(error)) { + LOG(ERROR) << "MessageFormat(" << msg.as_string() << ") failed with " + << u_errorName(error); + return string16(); + } + return string16(formatted.getBuffer(), formatted.length()); +} + +string16 MessageFormatter::FormatWithNamedArgs( + StringPiece16 msg, + StringPiece name0, const internal::MessageArg& arg0, + StringPiece name1, const internal::MessageArg& arg1, + StringPiece name2, const internal::MessageArg& arg2, + StringPiece name3, const internal::MessageArg& arg3, + StringPiece name4, const internal::MessageArg& arg4, + StringPiece name5, const internal::MessageArg& arg5, + StringPiece name6, const internal::MessageArg& arg6) { + icu::UnicodeString names[] = { + UnicodeStringFromStringPiece(name0), + UnicodeStringFromStringPiece(name1), + UnicodeStringFromStringPiece(name2), + UnicodeStringFromStringPiece(name3), + UnicodeStringFromStringPiece(name4), + UnicodeStringFromStringPiece(name5), + UnicodeStringFromStringPiece(name6), + }; + int32_t args_count = 0; + icu::Formattable args[] = { + arg0.has_value(&args_count) ? *arg0.formattable : icu::Formattable(), + arg1.has_value(&args_count) ? *arg1.formattable : icu::Formattable(), + arg2.has_value(&args_count) ? *arg2.formattable : icu::Formattable(), + arg3.has_value(&args_count) ? *arg3.formattable : icu::Formattable(), + arg4.has_value(&args_count) ? *arg4.formattable : icu::Formattable(), + arg5.has_value(&args_count) ? *arg5.formattable : icu::Formattable(), + arg6.has_value(&args_count) ? *arg6.formattable : icu::Formattable(), + }; + + UnicodeString msg_string(msg.data(), msg.size()); + UErrorCode error = U_ZERO_ERROR; + icu::MessageFormat format(msg_string, error); + + icu::UnicodeString formatted; + format.format(names, args, args_count, formatted, error); + if (U_FAILURE(error)) { + LOG(ERROR) << "MessageFormat(" << msg.as_string() << ") failed with " + << u_errorName(error); + return string16(); + } + return string16(formatted.getBuffer(), formatted.length()); +} + +} // namespace i18n +} // namespace base diff --git a/base/i18n/message_formatter.h b/base/i18n/message_formatter.h new file mode 100644 index 000000000000..bcdc3bc97752 --- /dev/null +++ b/base/i18n/message_formatter.h @@ -0,0 +1,111 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_I18N_MESSAGE_FORMATTER_H_ +#define BASE_I18N_MESSAGE_FORMATTER_H_ + +#include +#include + +#include "base/i18n/base_i18n_export.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "base/strings/string_piece.h" +#include "third_party/icu/source/common/unicode/uversion.h" + +U_NAMESPACE_BEGIN +class Formattable; +U_NAMESPACE_END + +namespace base { + +class Time; + +namespace i18n { + +class MessageFormatter; + +namespace internal { + +class BASE_I18N_EXPORT MessageArg { + public: + MessageArg(const char* s); + MessageArg(StringPiece s); + MessageArg(const std::string& s); + MessageArg(const string16& s); + MessageArg(int i); + MessageArg(int64_t i); + MessageArg(double d); + MessageArg(const Time& t); + ~MessageArg(); + + private: + friend class base::i18n::MessageFormatter; + MessageArg(); + // Tests if this argument has a value, and if so increments *count. + bool has_value(int* count) const; + scoped_ptr formattable; + DISALLOW_COPY_AND_ASSIGN(MessageArg); +}; + +} // namespace internal + +// Message Formatter with the ICU message format syntax support. +// It can format strings (UTF-8 and UTF-16), numbers and base::Time with +// plural, gender and other 'selectors' support. This is handy if you +// have multiple parameters of differnt types and some of them require +// plural or gender/selector support. +// +// To use this API for locale-sensitive formatting, retrieve a 'message +// template' in the ICU message format from a message bundle (e.g. with +// l10n_util::GetStringUTF16()) and pass it to FormatWith{Named,Numbered}Args. +// +// MessageFormat specs: +// http://icu-project.org/apiref/icu4j/com/ibm/icu/text/MessageFormat.html +// http://icu-project.org/apiref/icu4c/classicu_1_1DecimalFormat.html#details +// Examples: +// http://userguide.icu-project.org/formatparse/messages +// message_formatter_unittest.cc +// go/plurals inside Google. +// TODO(jshin): Document this API at sites.chromium.org and add a reference +// here. + +class BASE_I18N_EXPORT MessageFormatter { + public: + static string16 FormatWithNamedArgs( + StringPiece16 msg, + StringPiece name0 = StringPiece(), + const internal::MessageArg& arg0 = internal::MessageArg(), + StringPiece name1 = StringPiece(), + const internal::MessageArg& arg1 = internal::MessageArg(), + StringPiece name2 = StringPiece(), + const internal::MessageArg& arg2 = internal::MessageArg(), + StringPiece name3 = StringPiece(), + const internal::MessageArg& arg3 = internal::MessageArg(), + StringPiece name4 = StringPiece(), + const internal::MessageArg& arg4 = internal::MessageArg(), + StringPiece name5 = StringPiece(), + const internal::MessageArg& arg5 = internal::MessageArg(), + StringPiece name6 = StringPiece(), + const internal::MessageArg& arg6 = internal::MessageArg()); + + static string16 FormatWithNumberedArgs( + StringPiece16 msg, + const internal::MessageArg& arg0 = internal::MessageArg(), + const internal::MessageArg& arg1 = internal::MessageArg(), + const internal::MessageArg& arg2 = internal::MessageArg(), + const internal::MessageArg& arg3 = internal::MessageArg(), + const internal::MessageArg& arg4 = internal::MessageArg(), + const internal::MessageArg& arg5 = internal::MessageArg(), + const internal::MessageArg& arg6 = internal::MessageArg()); + + private: + MessageFormatter() {} + DISALLOW_COPY_AND_ASSIGN(MessageFormatter); +}; + +} // namespace i18n +} // namespace base + +#endif // BASE_I18N_MESSAGE_FORMATTER_H_ diff --git a/base/i18n/message_formatter_unittest.cc b/base/i18n/message_formatter_unittest.cc new file mode 100644 index 000000000000..85e2e171cb11 --- /dev/null +++ b/base/i18n/message_formatter_unittest.cc @@ -0,0 +1,180 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/i18n/message_formatter.h" + +#include "base/i18n/rtl.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/icu/source/common/unicode/unistr.h" +#include "third_party/icu/source/i18n/unicode/datefmt.h" +#include "third_party/icu/source/i18n/unicode/msgfmt.h" + +typedef testing::Test MessageFormatterTest; + +namespace base { +namespace i18n { + +class MessageFormatterTest : public testing::Test { + protected: + MessageFormatterTest() { + original_locale_ = GetConfiguredLocale(); + SetICUDefaultLocale("en-US"); + } + ~MessageFormatterTest() override { + SetICUDefaultLocale(original_locale_); + } + + private: + std::string original_locale_; +}; + +namespace { + +void AppendFormattedDateTime(const scoped_ptr& df, + const Time& now, std::string* result) { + icu::UnicodeString formatted; + df->format(static_cast(now.ToJsTime()), formatted). + toUTF8String(*result); +} + +} // namespace + +TEST_F(MessageFormatterTest, PluralNamedArgs) { + const string16 pattern = ASCIIToUTF16( + "{num_people, plural, " + "=0 {I met nobody in {place}.}" + "=1 {I met a person in {place}.}" + "other {I met # people in {place}.}}"); + + std::string result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs( + pattern, "num_people", 0, "place", "Paris")); + EXPECT_EQ("I met nobody in Paris.", result); + result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs( + pattern, "num_people", 1, "place", "Paris")); + EXPECT_EQ("I met a person in Paris.", result); + result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs( + pattern, "num_people", 5, "place", "Paris")); + EXPECT_EQ("I met 5 people in Paris.", result); +} + +TEST_F(MessageFormatterTest, PluralNamedArgsWithOffset) { + const string16 pattern = ASCIIToUTF16( + "{num_people, plural, offset:1 " + "=0 {I met nobody in {place}.}" + "=1 {I met {person} in {place}.}" + "=2 {I met {person} and one other person in {place}.}" + "=13 {I met {person} and a dozen other people in {place}.}" + "other {I met {person} and # other people in {place}.}}"); + + std::string result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs( + pattern, "num_people", 0, "place", "Paris")); + EXPECT_EQ("I met nobody in Paris.", result); + // {person} is ignored if {num_people} is 0. + result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs( + pattern, "num_people", 0, "place", "Paris", "person", "Peter")); + EXPECT_EQ("I met nobody in Paris.", result); + result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs( + pattern, "num_people", 1, "place", "Paris", "person", "Peter")); + EXPECT_EQ("I met Peter in Paris.", result); + result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs( + pattern, "num_people", 2, "place", "Paris", "person", "Peter")); + EXPECT_EQ("I met Peter and one other person in Paris.", result); + result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs( + pattern, "num_people", 13, "place", "Paris", "person", "Peter")); + EXPECT_EQ("I met Peter and a dozen other people in Paris.", result); + result = UTF16ToASCII(MessageFormatter::FormatWithNamedArgs( + pattern, "num_people", 50, "place", "Paris", "person", "Peter")); + EXPECT_EQ("I met Peter and 49 other people in Paris.", result); +} + +TEST_F(MessageFormatterTest, PluralNumberedArgs) { + const string16 pattern = ASCIIToUTF16( + "{1, plural, " + "=1 {The cert for {0} expired yesterday.}" + "=7 {The cert for {0} expired a week ago.}" + "other {The cert for {0} expired # days ago.}}"); + + std::string result = UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs( + pattern, "example.com", 1)); + EXPECT_EQ("The cert for example.com expired yesterday.", result); + result = UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs( + pattern, "example.com", 7)); + EXPECT_EQ("The cert for example.com expired a week ago.", result); + result = UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs( + pattern, "example.com", 15)); + EXPECT_EQ("The cert for example.com expired 15 days ago.", result); +} + +TEST_F(MessageFormatterTest, PluralNumberedArgsWithDate) { + const string16 pattern = ASCIIToUTF16( + "{1, plural, " + "=1 {The cert for {0} expired yesterday. Today is {2,date,full}}" + "other {The cert for {0} expired # days ago. Today is {2,date,full}}}"); + + base::Time now = base::Time::Now(); + using icu::DateFormat; + scoped_ptr df(DateFormat::createDateInstance(DateFormat::FULL)); + std::string second_sentence = " Today is "; + AppendFormattedDateTime(df, now, &second_sentence); + + std::string result = UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs( + pattern, "example.com", 1, now)); + EXPECT_EQ("The cert for example.com expired yesterday." + second_sentence, + result); + result = UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs( + pattern, "example.com", 15, now)); + EXPECT_EQ("The cert for example.com expired 15 days ago." + second_sentence, + result); +} + +TEST_F(MessageFormatterTest, DateTimeAndNumber) { + // Note that using 'mph' for all locales is not a good i18n practice. + const string16 pattern = ASCIIToUTF16( + "At {0,time, short} on {0,date, medium}, " + "there was {1} at building {2,number,integer}. " + "The speed of the wind was {3,number,###.#} mph."); + + using icu::DateFormat; + scoped_ptr tf(DateFormat::createTimeInstance(DateFormat::SHORT)); + scoped_ptr df(DateFormat::createDateInstance(DateFormat::MEDIUM)); + + base::Time now = base::Time::Now(); + std::string expected = "At "; + AppendFormattedDateTime(tf, now, &expected); + expected.append(" on "); + AppendFormattedDateTime(df, now, &expected); + expected.append(", there was an explosion at building 3. " + "The speed of the wind was 37.4 mph."); + + EXPECT_EQ(expected, UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs( + pattern, now, "an explosion", 3, 37.413))); +} + +TEST_F(MessageFormatterTest, SelectorSingleOrMultiple) { + const string16 pattern = ASCIIToUTF16( + "{0, select," + "single {Select a file to upload.}" + "multiple {Select files to upload.}" + "other {UNUSED}}"); + + std::string result = UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs( + pattern, "single")); + EXPECT_EQ("Select a file to upload.", result); + result = UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs( + pattern, "multiple")); + EXPECT_EQ("Select files to upload.", result); + + // fallback if a parameter is not selectors specified in the message pattern. + result = UTF16ToASCII(MessageFormatter::FormatWithNumberedArgs( + pattern, "foobar")); + EXPECT_EQ("UNUSED", result); +} + +} // namespace i18n +} // namespace base diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 18b557ba9c4a..f52b44288c86 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -2723,15 +2723,19 @@ Even if you have downloaded files from this website before, the website might ha Server's certificate does not match the URL. - - This server could not prove that it is <strong>$1paypal.com</strong>; its security certificate expired $23 day(s) ago. This may be caused by a misconfiguration or an attacker intercepting your connection. Your computer's clock is currently set to $3July 18, 2012. Does that look right? If not, you should correct your system's clock and then refresh this page. + + {1, plural, + =1 {This server could not prove that it is <strong>{0}paypal.com</strong>; its security certificate expired yesterday. This may be caused by a misconfiguration or an attacker intercepting your connection. Your computer's clock is currently set to {2, date, full}Monday, July 16, 2012. Does that look right? If not, you should correct your system's clock and then refresh this page.} + other {This server could not prove that it is <strong>{0}paypal.com</strong>; its security certificate expired # days ago. This may be caused by a misconfiguration or an attacker intercepting your connection. Your computer's clock is currently set to {2, date, full}Monday, July 16, 2012. Does that look right? If not, you should correct your system's clock and then refresh this page.}} Server's certificate has expired. - - This server could not prove that it is <strong>$1paypal.com</strong>; its security certificate is supposedly from $23 day(s) in the future. This may be caused by a misconfiguration or an attacker intercepting your connection. + + {1, plural, + =1 {This server could not prove that it is <strong>{0}paypal.com</strong>; its security certificate is supposedly from tomorrow. This may be caused by a misconfiguration or an attacker intercepting your connection.} + other {This server could not prove that it is <strong>{0}paypal.com</strong>; its security certificate is supposedly from # days in the future. This may be caused by a misconfiguration or an attacker intercepting your connection.}} Server's certificate is not yet valid. diff --git a/chrome/browser/ssl/ssl_error_info.cc b/chrome/browser/ssl/ssl_error_info.cc index 00916e8c6326..9b36fb7cd1c7 100644 --- a/chrome/browser/ssl/ssl_error_info.cc +++ b/chrome/browser/ssl/ssl_error_info.cc @@ -4,8 +4,7 @@ #include "chrome/browser/ssl/ssl_error_info.h" -#include "base/i18n/time_formatting.h" -#include "base/strings/string_number_conversions.h" +#include "base/i18n/message_formatter.h" #include "base/strings/utf_string_conversions.h" #include "chrome/grit/chromium_strings.h" #include "chrome/grit/generated_resources.h" @@ -63,18 +62,16 @@ SSLErrorInfo SSLErrorInfo::CreateError(ErrorType error_type, // the expiration value (https://crbug.com/476758). int expiration_value = (base::Time::Now() - cert->valid_expiry()).InDays() + 1; - details = l10n_util::GetStringFUTF16( - IDS_CERT_ERROR_EXPIRED_DETAILS, UTF8ToUTF16(request_url.host()), - base::IntToString16(expiration_value), - base::TimeFormatFriendlyDate(base::Time::Now())); + details = base::i18n::MessageFormatter::FormatWithNumberedArgs( + l10n_util::GetStringUTF16(IDS_CERT_ERROR_EXPIRED_DETAILS), + request_url.host(), expiration_value, base::Time::Now()); short_description = l10n_util::GetStringUTF16(IDS_CERT_ERROR_EXPIRED_DESCRIPTION); } else if (base::Time::Now() < cert->valid_start()) { - details = l10n_util::GetStringFUTF16( - IDS_CERT_ERROR_NOT_YET_VALID_DETAILS, - UTF8ToUTF16(request_url.host()), - base::IntToString16( - (cert->valid_start() - base::Time::Now()).InDays())); + details = base::i18n::MessageFormatter::FormatWithNumberedArgs( + l10n_util::GetStringUTF16(IDS_CERT_ERROR_NOT_YET_VALID_DETAILS), + request_url.host(), + (cert->valid_start() - base::Time::Now()).InDays()); short_description = l10n_util::GetStringUTF16(IDS_CERT_ERROR_NOT_YET_VALID_DESCRIPTION); } else { diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn index 29791b49cdac..8e75e4cc5725 100644 --- a/extensions/browser/BUILD.gn +++ b/extensions/browser/BUILD.gn @@ -11,6 +11,7 @@ source_set("browser") { sources = [] deps = [ + "//base:i18n", "//components/guest_view/browser", "//components/keyed_service/content", "//components/keyed_service/core", diff --git a/extensions/browser/api/device_permissions_prompt.cc b/extensions/browser/api/device_permissions_prompt.cc index 941f883a825b..80697482333f 100644 --- a/extensions/browser/api/device_permissions_prompt.cc +++ b/extensions/browser/api/device_permissions_prompt.cc @@ -5,6 +5,7 @@ #include "extensions/browser/api/device_permissions_prompt.h" #include "base/bind.h" +#include "base/i18n/message_formatter.h" #include "base/scoped_observer.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" @@ -91,9 +92,8 @@ class UsbDevicePermissionsPrompt : public DevicePermissionsPrompt::Prompt, } base::string16 GetHeading() const override { - return l10n_util::GetStringUTF16( - multiple() ? IDS_USB_DEVICE_PERMISSIONS_PROMPT_TITLE_MULTIPLE - : IDS_USB_DEVICE_PERMISSIONS_PROMPT_TITLE_SINGLE); + return l10n_util::GetSingleOrMultipleStringUTF16( + IDS_USB_DEVICE_PERMISSIONS_PROMPT_TITLE, multiple()); } void Dismissed() override { @@ -206,9 +206,8 @@ class HidDevicePermissionsPrompt : public DevicePermissionsPrompt::Prompt, } base::string16 GetHeading() const override { - return l10n_util::GetStringUTF16( - multiple() ? IDS_HID_DEVICE_PERMISSIONS_PROMPT_TITLE_MULTIPLE - : IDS_HID_DEVICE_PERMISSIONS_PROMPT_TITLE_SINGLE); + return l10n_util::GetSingleOrMultipleStringUTF16( + IDS_HID_DEVICE_PERMISSIONS_PROMPT_TITLE, multiple()); } void Dismissed() override { @@ -308,10 +307,9 @@ void DevicePermissionsPrompt::Prompt::SetObserver(Observer* observer) { } base::string16 DevicePermissionsPrompt::Prompt::GetPromptMessage() const { - return l10n_util::GetStringFUTF16(multiple_ - ? IDS_DEVICE_PERMISSIONS_PROMPT_MULTIPLE - : IDS_DEVICE_PERMISSIONS_PROMPT_SINGLE, - base::UTF8ToUTF16(extension_->name())); + return base::i18n::MessageFormatter::FormatWithNumberedArgs( + l10n_util::GetStringUTF16(IDS_DEVICE_PERMISSIONS_PROMPT), + multiple_ ? "multiple" : "single", extension_->name()); } base::string16 DevicePermissionsPrompt::Prompt::GetDeviceName( diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp index ec9c663f7d35..f7c2fd688f1e 100644 --- a/extensions/extensions.gyp +++ b/extensions/extensions.gyp @@ -123,6 +123,7 @@ 'type': 'static_library', 'dependencies': [ '../base/base.gyp:base', + '../base/base.gyp:base_i18n', '../base/base.gyp:base_prefs', '../components/components.gyp:browsing_data', '../components/components.gyp:device_event_log_component', diff --git a/extensions/extensions_strings.grd b/extensions/extensions_strings.grd index dd407b067645..3e602b096350 100644 --- a/extensions/extensions_strings.grd +++ b/extensions/extensions_strings.grd @@ -365,23 +365,21 @@ Unknown product $11234 from vendor $2abcd (serial number $300001) - - The application "$1Chrome Dev Editor" is requesting access to one or more of your devices. - - - The application "$1Chrome Dev Editor" is requesting access to one of your devices. - - - Select HID devices - - - Select a HID device - - - Select USB devices - - - Select a USB device + + {0, select, + single {The application "{1}Chrome Dev Editor" is requesting access to one of your devices.} + multiple {The application "{1}Chrome Dev Editor" is requesting access to one or more of your devices.} + other {UNUSED}} + + + {1, select, + single {Select a HID device} multiple {Select HID devices} + other {UNUSED}} + + + {1, select, + single {Select a USB device} multiple {Select USB devices} + other {UNUSED}} Configure network connections diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn index cc416e3afbd4..15ad86c835b8 100644 --- a/ui/base/BUILD.gn +++ b/ui/base/BUILD.gn @@ -167,8 +167,6 @@ component("base") { "l10n/l10n_util_collator.h", "l10n/l10n_util_mac.h", "l10n/l10n_util_mac.mm", - "l10n/l10n_util_plurals.cc", - "l10n/l10n_util_plurals.h", "l10n/l10n_util_posix.cc", "l10n/l10n_util_win.cc", "l10n/l10n_util_win.h", diff --git a/ui/base/l10n/formatter.cc b/ui/base/l10n/formatter.cc index 7f7193304591..298646d9e2c6 100644 --- a/ui/base/l10n/formatter.cc +++ b/ui/base/l10n/formatter.cc @@ -7,10 +7,10 @@ #include #include "base/logging.h" +#include "base/memory/scoped_ptr.h" #include "third_party/icu/source/common/unicode/unistr.h" #include "third_party/icu/source/i18n/unicode/msgfmt.h" #include "ui/base/l10n/l10n_util.h" -#include "ui/base/l10n/l10n_util_plurals.h" #include "ui/strings/grit/ui_strings.h" namespace ui { @@ -139,6 +139,33 @@ static const Pluralities IDS_DURATION_HOUR_2ND = { " other{# hours}" }; +namespace { + +scoped_ptr BuildPluralRules() { + UErrorCode err = U_ZERO_ERROR; + scoped_ptr rules( + icu::PluralRules::forLocale(icu::Locale::getDefault(), err)); + if (U_FAILURE(err)) { + err = U_ZERO_ERROR; + icu::UnicodeString fallback_rules("one: n is 1", -1, US_INV); + rules.reset(icu::PluralRules::createRules(fallback_rules, err)); + DCHECK(U_SUCCESS(err)); + } + return rules.Pass(); +} + +void FormatNumberInPlural(const icu::MessageFormat& format, int number, + icu::UnicodeString* result, UErrorCode* err) { + if (U_FAILURE(*err)) return; + icu::Formattable formattable(number); + icu::FieldPosition ignore(icu::FieldPosition::DONT_CARE); + format.format(&formattable, 1, *result, ignore, *err); + DCHECK(U_SUCCESS(*err)); + return; +} + +} // namespace + Formatter::Formatter(const Pluralities& sec_pluralities, const Pluralities& min_pluralities, const Pluralities& hour_pluralities, @@ -177,8 +204,8 @@ void Formatter::Format(Unit unit, DCHECK(simple_format_[unit]); DCHECK(formatted_string->isEmpty() == TRUE); UErrorCode error = U_ZERO_ERROR; - l10n_util::FormatNumberInPlural(*simple_format_[unit], - value, formatted_string, &error); + FormatNumberInPlural(*simple_format_[unit], + value, formatted_string, &error); DCHECK(U_SUCCESS(error)) << "Error in icu::PluralFormat::format()."; return; } @@ -193,11 +220,11 @@ void Formatter::Format(TwoUnits units, << "Detailed() not implemented for your (format, length) combination!"; DCHECK(formatted_string->isEmpty() == TRUE); UErrorCode error = U_ZERO_ERROR; - l10n_util::FormatNumberInPlural(*detailed_format_[units][0], value_1, - formatted_string, &error); + FormatNumberInPlural(*detailed_format_[units][0], value_1, + formatted_string, &error); DCHECK(U_SUCCESS(error)); - l10n_util::FormatNumberInPlural(*detailed_format_[units][1], value_2, - formatted_string, &error); + FormatNumberInPlural(*detailed_format_[units][1], value_2, + formatted_string, &error); DCHECK(U_SUCCESS(error)); return; } @@ -230,7 +257,7 @@ scoped_ptr Formatter::InitFormat( return format.Pass(); } - scoped_ptr rules(l10n_util::BuildPluralRules()); + scoped_ptr rules(BuildPluralRules()); return CreateFallbackFormat(*rules, pluralities); } diff --git a/ui/base/l10n/l10n_util.cc b/ui/base/l10n/l10n_util.cc index 35b81aa56800..c054b727bcd9 100644 --- a/ui/base/l10n/l10n_util.cc +++ b/ui/base/l10n/l10n_util.cc @@ -13,6 +13,7 @@ #include "base/compiler_specific.h" #include "base/files/file_util.h" #include "base/i18n/file_util_icu.h" +#include "base/i18n/message_formatter.h" #include "base/i18n/rtl.h" #include "base/i18n/string_compare.h" #include "base/lazy_instance.h" @@ -27,7 +28,6 @@ #include "third_party/icu/source/common/unicode/rbbi.h" #include "third_party/icu/source/common/unicode/uloc.h" #include "ui/base/l10n/l10n_util_collator.h" -#include "ui/base/l10n/l10n_util_plurals.h" #include "ui/base/resource/resource_bundle.h" #include "ui/base/ui_base_paths.h" @@ -825,25 +825,20 @@ base::string16 GetStringFUTF16Int(int message_id, int64 a) { } base::string16 GetPluralStringFUTF16(int message_id, int number) { - base::string16 pattern = GetStringUTF16(message_id); - UErrorCode err = U_ZERO_ERROR; - icu::MessageFormat format( - icu::UnicodeString(FALSE, pattern.data(), pattern.length()), err); - icu::UnicodeString result_unistring; - FormatNumberInPlural(format, number, &result_unistring, &err); - int capacity = result_unistring.length() + 1; - DCHECK_GT(capacity, 1); - base::string16 result; - result_unistring.extract( - static_cast(base::WriteInto(&result, capacity)), capacity, err); - DCHECK(U_SUCCESS(err)); - return result; + return base::i18n::MessageFormatter::FormatWithNumberedArgs( + GetStringUTF16(message_id), number); } std::string GetPluralStringFUTF8(int message_id, int number) { return base::UTF16ToUTF8(GetPluralStringFUTF16(message_id, number)); } +base::string16 GetSingleOrMultipleStringUTF16(int message_id, + bool is_multiple) { + return base::i18n::MessageFormatter::FormatWithNumberedArgs( + GetStringUTF16(message_id), is_multiple ? "multiple" : "single"); +} + void SortStrings16(const std::string& locale, std::vector* strings) { SortVectorWithStringKey(locale, strings, false); diff --git a/ui/base/l10n/l10n_util.h b/ui/base/l10n/l10n_util.h index 505ebd3a41fd..9317556260f6 100644 --- a/ui/base/l10n/l10n_util.h +++ b/ui/base/l10n/l10n_util.h @@ -169,6 +169,13 @@ base::string16 GetStringFUTF16Int(int message_id, int64 a); UI_BASE_EXPORT base::string16 GetPluralStringFUTF16(int message_id, int number); UI_BASE_EXPORT std::string GetPluralStringFUTF8(int message_id, int number); +// Get a string when you only care about 'single vs multiple' distinction. +// The message pointed to by |message_id| should be in ICU syntax +// (see the references above for Plural) with 'single', 'multiple', and +// 'other' (fallback) instead of 'male', 'female', and 'other' (fallback). +UI_BASE_EXPORT base::string16 GetSingleOrMultipleStringUTF16(int message_id, + bool is_multiple); + // In place sorting of base::string16 strings using collation rules for // |locale|. UI_BASE_EXPORT void SortStrings16(const std::string& locale, diff --git a/ui/base/l10n/l10n_util_plurals.cc b/ui/base/l10n/l10n_util_plurals.cc deleted file mode 100644 index 177e263cf244..000000000000 --- a/ui/base/l10n/l10n_util_plurals.cc +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "ui/base/l10n/l10n_util_plurals.h" - -#include "base/logging.h" -#include "base/memory/scoped_ptr.h" -#include "ui/base/l10n/l10n_util.h" - -namespace l10n_util { - -scoped_ptr BuildPluralRules() { - UErrorCode err = U_ZERO_ERROR; - scoped_ptr rules( - icu::PluralRules::forLocale(icu::Locale::getDefault(), err)); - if (U_FAILURE(err)) { - err = U_ZERO_ERROR; - icu::UnicodeString fallback_rules("one: n is 1", -1, US_INV); - rules.reset(icu::PluralRules::createRules(fallback_rules, err)); - DCHECK(U_SUCCESS(err)); - } - return rules.Pass(); -} - -void FormatNumberInPlural(const icu::MessageFormat& format, int number, - icu::UnicodeString* result, UErrorCode* err) { - if (U_FAILURE(*err)) return; - icu::Formattable formattable(number); - icu::FieldPosition ignore(icu::FieldPosition::DONT_CARE); - format.format(&formattable, 1, *result, ignore, *err); - DCHECK(U_SUCCESS(*err)); - return; -} - - -} // namespace l10n_util diff --git a/ui/base/l10n/l10n_util_plurals.h b/ui/base/l10n/l10n_util_plurals.h deleted file mode 100644 index efae5d685ab0..000000000000 --- a/ui/base/l10n/l10n_util_plurals.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This file contains utility functions for dealing with pluralization of -// localized content. - -#ifndef UI_BASE_L10N_L10N_UTIL_PLURALS_H_ -#define UI_BASE_L10N_L10N_UTIL_PLURALS_H_ - -#include -#include - -#include "base/memory/scoped_ptr.h" -#include "third_party/icu/source/i18n/unicode/msgfmt.h" -#include "third_party/icu/source/i18n/unicode/plurrule.h" - -namespace l10n_util { - -// Returns a PluralRules for the current locale. -scoped_ptr BuildPluralRules(); - -// Formats |number| using MessageFormat |format| and appends the result -// to |append_to|. -// |format| has to be generated with ICU's plural message format. -// See http://userguide.icu-project.org/formatparse/messages -void FormatNumberInPlural(const icu::MessageFormat& format, int number, - icu::UnicodeString* append_to, UErrorCode* err); - -} // namespace l10n_util - -#endif // UI_BASE_L10N_L10N_UTIL_PLURALS_H_ diff --git a/ui/base/ui_base.gyp b/ui/base/ui_base.gyp index 3e8375cc4a60..36f07f760a13 100644 --- a/ui/base/ui_base.gyp +++ b/ui/base/ui_base.gyp @@ -216,8 +216,6 @@ 'l10n/l10n_util_collator.h', 'l10n/l10n_util_mac.h', 'l10n/l10n_util_mac.mm', - 'l10n/l10n_util_plurals.cc', - 'l10n/l10n_util_plurals.h', 'l10n/l10n_util_posix.cc', 'l10n/l10n_util_win.cc', 'l10n/l10n_util_win.h', -- 2.11.4.GIT