Add ICU message format support
[chromium-blink-merge.git] / ui / base / l10n / formatter.cc
blob298646d9e2c61245113821ea2aaf929435e74a08
1 // Copyright 2014 The Chromium 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.
5 #include "ui/base/l10n/formatter.h"
7 #include <vector>
9 #include "base/logging.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "third_party/icu/source/common/unicode/unistr.h"
12 #include "third_party/icu/source/i18n/unicode/msgfmt.h"
13 #include "ui/base/l10n/l10n_util.h"
14 #include "ui/strings/grit/ui_strings.h"
16 namespace ui {
18 UI_BASE_EXPORT bool formatter_force_fallback = false;
20 struct Pluralities {
21 int id;
22 const char* const fallback_one;
23 const char* const fallback_other;
26 static const Pluralities IDS_ELAPSED_SHORT_SEC = {
27 IDS_TIME_ELAPSED_SECS,
28 "one{# sec ago}",
29 " other{# secs ago}"
31 static const Pluralities IDS_ELAPSED_SHORT_MIN = {
32 IDS_TIME_ELAPSED_MINS,
33 "one{# min ago}",
34 " other{# mins ago}"
36 static const Pluralities IDS_ELAPSED_HOUR = {
37 IDS_TIME_ELAPSED_HOURS,
38 "one{# hour ago}",
39 " other{# hours ago}"
41 static const Pluralities IDS_ELAPSED_DAY = {
42 IDS_TIME_ELAPSED_DAYS,
43 "one{# day ago}",
44 " other{# days ago}"
47 static const Pluralities IDS_REMAINING_SHORT_SEC = {
48 IDS_TIME_REMAINING_SECS,
49 "one{# sec left}",
50 " other{# secs left}"
52 static const Pluralities IDS_REMAINING_SHORT_MIN = {
53 IDS_TIME_REMAINING_MINS,
54 "one{# min left}",
55 " other{# mins left}"
58 static const Pluralities IDS_REMAINING_LONG_SEC = {
59 IDS_TIME_REMAINING_LONG_SECS,
60 "one{# second left}",
61 " other{# seconds left}"
63 static const Pluralities IDS_REMAINING_LONG_MIN = {
64 IDS_TIME_REMAINING_LONG_MINS,
65 "one{# minute left}",
66 " other{# minutes left}"
68 static const Pluralities IDS_REMAINING_HOUR = {
69 IDS_TIME_REMAINING_HOURS,
70 "one{# hour left}",
71 " other{# hours left}"
73 static const Pluralities IDS_REMAINING_DAY = {
74 IDS_TIME_REMAINING_DAYS,
75 "one{# day left}",
76 " other{# days left}"
79 static const Pluralities IDS_DURATION_SHORT_SEC = {
80 IDS_TIME_SECS,
81 "one{# sec}",
82 " other{# secs}"
84 static const Pluralities IDS_DURATION_SHORT_MIN = {
85 IDS_TIME_MINS,
86 "one{# min}",
87 " other{# mins}"
90 static const Pluralities IDS_LONG_SEC = {
91 IDS_TIME_LONG_SECS,
92 "one{# second}",
93 " other{# seconds}"
95 static const Pluralities IDS_LONG_MIN = {
96 IDS_TIME_LONG_MINS,
97 "one{# minute}",
98 " other{# minutes}"
100 static const Pluralities IDS_DURATION_HOUR = {
101 IDS_TIME_HOURS,
102 "one{# hour}",
103 " other{# hours}"
105 static const Pluralities IDS_DURATION_DAY = {
106 IDS_TIME_DAYS,
107 "one{# day}",
108 " other{# days}"
111 static const Pluralities IDS_LONG_MIN_1ST = {
112 IDS_TIME_LONG_MINS_1ST,
113 "one{# minute and }",
114 " other{# minutes and }"
116 static const Pluralities IDS_LONG_SEC_2ND = {
117 IDS_TIME_LONG_SECS_2ND,
118 "one{# second}",
119 " other{# seconds}"
121 static const Pluralities IDS_DURATION_HOUR_1ST = {
122 IDS_TIME_HOURS_1ST,
123 "one{# hour and }",
124 " other{# hours and }"
126 static const Pluralities IDS_LONG_MIN_2ND = {
127 IDS_TIME_LONG_MINS_2ND,
128 "one{# minute}",
129 " other{# minutes}"
131 static const Pluralities IDS_DURATION_DAY_1ST = {
132 IDS_TIME_DAYS_1ST,
133 "one{# day and }",
134 " other{# days and }"
136 static const Pluralities IDS_DURATION_HOUR_2ND = {
137 IDS_TIME_HOURS_2ND,
138 "one{# hour}",
139 " other{# hours}"
142 namespace {
144 scoped_ptr<icu::PluralRules> BuildPluralRules() {
145 UErrorCode err = U_ZERO_ERROR;
146 scoped_ptr<icu::PluralRules> rules(
147 icu::PluralRules::forLocale(icu::Locale::getDefault(), err));
148 if (U_FAILURE(err)) {
149 err = U_ZERO_ERROR;
150 icu::UnicodeString fallback_rules("one: n is 1", -1, US_INV);
151 rules.reset(icu::PluralRules::createRules(fallback_rules, err));
152 DCHECK(U_SUCCESS(err));
154 return rules.Pass();
157 void FormatNumberInPlural(const icu::MessageFormat& format, int number,
158 icu::UnicodeString* result, UErrorCode* err) {
159 if (U_FAILURE(*err)) return;
160 icu::Formattable formattable(number);
161 icu::FieldPosition ignore(icu::FieldPosition::DONT_CARE);
162 format.format(&formattable, 1, *result, ignore, *err);
163 DCHECK(U_SUCCESS(*err));
164 return;
167 } // namespace
169 Formatter::Formatter(const Pluralities& sec_pluralities,
170 const Pluralities& min_pluralities,
171 const Pluralities& hour_pluralities,
172 const Pluralities& day_pluralities) {
173 simple_format_[UNIT_SEC] = InitFormat(sec_pluralities);
174 simple_format_[UNIT_MIN] = InitFormat(min_pluralities);
175 simple_format_[UNIT_HOUR] = InitFormat(hour_pluralities);
176 simple_format_[UNIT_DAY] = InitFormat(day_pluralities);
179 Formatter::Formatter(const Pluralities& sec_pluralities,
180 const Pluralities& min_pluralities,
181 const Pluralities& hour_pluralities,
182 const Pluralities& day_pluralities,
183 const Pluralities& min_sec_pluralities1,
184 const Pluralities& min_sec_pluralities2,
185 const Pluralities& hour_min_pluralities1,
186 const Pluralities& hour_min_pluralities2,
187 const Pluralities& day_hour_pluralities1,
188 const Pluralities& day_hour_pluralities2) {
189 simple_format_[UNIT_SEC] = InitFormat(sec_pluralities);
190 simple_format_[UNIT_MIN] = InitFormat(min_pluralities);
191 simple_format_[UNIT_HOUR] = InitFormat(hour_pluralities);
192 simple_format_[UNIT_DAY] = InitFormat(day_pluralities);
193 detailed_format_[TWO_UNITS_MIN_SEC][0] = InitFormat(min_sec_pluralities1);
194 detailed_format_[TWO_UNITS_MIN_SEC][1] = InitFormat(min_sec_pluralities2);
195 detailed_format_[TWO_UNITS_HOUR_MIN][0] = InitFormat(hour_min_pluralities1);
196 detailed_format_[TWO_UNITS_HOUR_MIN][1] = InitFormat(hour_min_pluralities2);
197 detailed_format_[TWO_UNITS_DAY_HOUR][0] = InitFormat(day_hour_pluralities1);
198 detailed_format_[TWO_UNITS_DAY_HOUR][1] = InitFormat(day_hour_pluralities2);
201 void Formatter::Format(Unit unit,
202 int value,
203 icu::UnicodeString* formatted_string) const {
204 DCHECK(simple_format_[unit]);
205 DCHECK(formatted_string->isEmpty() == TRUE);
206 UErrorCode error = U_ZERO_ERROR;
207 FormatNumberInPlural(*simple_format_[unit],
208 value, formatted_string, &error);
209 DCHECK(U_SUCCESS(error)) << "Error in icu::PluralFormat::format().";
210 return;
213 void Formatter::Format(TwoUnits units,
214 int value_1,
215 int value_2,
216 icu::UnicodeString* formatted_string) const {
217 DCHECK(detailed_format_[units][0])
218 << "Detailed() not implemented for your (format, length) combination!";
219 DCHECK(detailed_format_[units][1])
220 << "Detailed() not implemented for your (format, length) combination!";
221 DCHECK(formatted_string->isEmpty() == TRUE);
222 UErrorCode error = U_ZERO_ERROR;
223 FormatNumberInPlural(*detailed_format_[units][0], value_1,
224 formatted_string, &error);
225 DCHECK(U_SUCCESS(error));
226 FormatNumberInPlural(*detailed_format_[units][1], value_2,
227 formatted_string, &error);
228 DCHECK(U_SUCCESS(error));
229 return;
232 scoped_ptr<icu::MessageFormat> Formatter::CreateFallbackFormat(
233 const icu::PluralRules& rules,
234 const Pluralities& pluralities) const {
235 icu::UnicodeString pattern("{NUMBER, plural, ");
236 if (rules.isKeyword(UNICODE_STRING_SIMPLE("one")))
237 pattern += icu::UnicodeString(pluralities.fallback_one);
238 pattern += icu::UnicodeString(pluralities.fallback_other);
239 pattern.append(UChar(0x7du)); // "}" = U+007D
241 UErrorCode error = U_ZERO_ERROR;
242 scoped_ptr<icu::MessageFormat> format(
243 new icu::MessageFormat(pattern, error));
244 DCHECK(U_SUCCESS(error));
245 return format.Pass();
248 scoped_ptr<icu::MessageFormat> Formatter::InitFormat(
249 const Pluralities& pluralities) {
250 if (!formatter_force_fallback) {
251 base::string16 pattern = l10n_util::GetStringUTF16(pluralities.id);
252 UErrorCode error = U_ZERO_ERROR;
253 scoped_ptr<icu::MessageFormat> format(new icu::MessageFormat(
254 icu::UnicodeString(FALSE, pattern.data(), pattern.length()), error));
255 DCHECK(U_SUCCESS(error));
256 if (format.get())
257 return format.Pass();
260 scoped_ptr<icu::PluralRules> rules(BuildPluralRules());
261 return CreateFallbackFormat(*rules, pluralities);
264 const Formatter* FormatterContainer::Get(TimeFormat::Format format,
265 TimeFormat::Length length) const {
266 DCHECK(formatter_[format][length])
267 << "Combination of FORMAT_ELAPSED and LENGTH_LONG is not implemented!";
268 return formatter_[format][length].get();
271 FormatterContainer::FormatterContainer() {
272 Initialize();
275 FormatterContainer::~FormatterContainer() {
278 void FormatterContainer::Initialize() {
279 formatter_[TimeFormat::FORMAT_ELAPSED][TimeFormat::LENGTH_SHORT].reset(
280 new Formatter(IDS_ELAPSED_SHORT_SEC,
281 IDS_ELAPSED_SHORT_MIN,
282 IDS_ELAPSED_HOUR,
283 IDS_ELAPSED_DAY));
284 formatter_[TimeFormat::FORMAT_ELAPSED][TimeFormat::LENGTH_LONG].reset();
285 formatter_[TimeFormat::FORMAT_REMAINING][TimeFormat::LENGTH_SHORT].reset(
286 new Formatter(IDS_REMAINING_SHORT_SEC,
287 IDS_REMAINING_SHORT_MIN,
288 IDS_REMAINING_HOUR,
289 IDS_REMAINING_DAY));
290 formatter_[TimeFormat::FORMAT_REMAINING][TimeFormat::LENGTH_LONG].reset(
291 new Formatter(IDS_REMAINING_LONG_SEC,
292 IDS_REMAINING_LONG_MIN,
293 IDS_REMAINING_HOUR,
294 IDS_REMAINING_DAY));
295 formatter_[TimeFormat::FORMAT_DURATION][TimeFormat::LENGTH_SHORT].reset(
296 new Formatter(IDS_DURATION_SHORT_SEC,
297 IDS_DURATION_SHORT_MIN,
298 IDS_DURATION_HOUR,
299 IDS_DURATION_DAY));
300 formatter_[TimeFormat::FORMAT_DURATION][TimeFormat::LENGTH_LONG].reset(
301 new Formatter(IDS_LONG_SEC,
302 IDS_LONG_MIN,
303 IDS_DURATION_HOUR,
304 IDS_DURATION_DAY,
305 IDS_LONG_MIN_1ST,
306 IDS_LONG_SEC_2ND,
307 IDS_DURATION_HOUR_1ST,
308 IDS_LONG_MIN_2ND,
309 IDS_DURATION_DAY_1ST,
310 IDS_DURATION_HOUR_2ND));
313 void FormatterContainer::Shutdown() {
314 for (int format = 0; format < TimeFormat::FORMAT_COUNT; ++format) {
315 for (int length = 0; length < TimeFormat::LENGTH_COUNT; ++length) {
316 formatter_[format][length].reset();
321 } // namespace ui