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.
4
5 #include "ui/base/l10n/formatter.h"
6
7 #include <limits.h>
8
9 #include <memory>
10 #include <vector>
11
12 #include "base/logging.h"
13 #include "third_party/icu/source/common/unicode/unistr.h"
14 #include "third_party/icu/source/i18n/unicode/msgfmt.h"
15 #include "ui/base/l10n/l10n_util.h"
16 #include "ui/strings/grit/ui_strings.h"
17
18 namespace ui {
19
20 UI_BASE_EXPORT bool formatter_force_fallback = false;
21
22 struct Pluralities {
23 int id;
24 const char* const fallback_one;
25 const char* const fallback_other;
26 };
27
28 static const Pluralities IDS_ELAPSED_SHORT_SEC = {
29 IDS_TIME_ELAPSED_SECS,
30 "one{# sec ago}",
31 " other{# secs ago}"
32 };
33
34 static const Pluralities IDS_ELAPSED_LONG_SEC = {
35 IDS_TIME_ELAPSED_LONG_SECS, "one{# second ago}", " other{# seconds ago}"};
36
37 static const Pluralities IDS_ELAPSED_SHORT_MIN = {
38 IDS_TIME_ELAPSED_MINS,
39 "one{# min ago}",
40 " other{# mins ago}"
41 };
42
43 static const Pluralities IDS_ELAPSED_LONG_MIN = {
44 IDS_TIME_ELAPSED_LONG_MINS, "one{# minute ago}", " other{# minutes ago}"};
45
46 static const Pluralities IDS_ELAPSED_HOUR = {
47 IDS_TIME_ELAPSED_HOURS,
48 "one{# hour ago}",
49 " other{# hours ago}"
50 };
51 static const Pluralities IDS_ELAPSED_DAY = {
52 IDS_TIME_ELAPSED_DAYS,
53 "one{# day ago}",
54 " other{# days ago}"
55 };
56 static const Pluralities IDS_ELAPSED_MONTH = {
57 IDS_TIME_ELAPSED_MONTHS, "one{# month ago}", " other{# months ago}"};
58 static const Pluralities IDS_ELAPSED_YEAR = {
59 IDS_TIME_ELAPSED_YEARS, "one{# year ago}", " other{# years ago}"};
60
61 static const Pluralities IDS_REMAINING_SHORT_SEC = {
62 IDS_TIME_REMAINING_SECS,
63 "one{# sec left}",
64 " other{# secs left}"
65 };
66 static const Pluralities IDS_REMAINING_SHORT_MIN = {
67 IDS_TIME_REMAINING_MINS,
68 "one{# min left}",
69 " other{# mins left}"
70 };
71
72 static const Pluralities IDS_REMAINING_LONG_SEC = {
73 IDS_TIME_REMAINING_LONG_SECS,
74 "one{# second left}",
75 " other{# seconds left}"
76 };
77 static const Pluralities IDS_REMAINING_LONG_MIN = {
78 IDS_TIME_REMAINING_LONG_MINS,
79 "one{# minute left}",
80 " other{# minutes left}"
81 };
82 static const Pluralities IDS_REMAINING_HOUR = {
83 IDS_TIME_REMAINING_HOURS,
84 "one{# hour left}",
85 " other{# hours left}"
86 };
87 static const Pluralities IDS_REMAINING_DAY = {
88 IDS_TIME_REMAINING_DAYS,
89 "one{# day left}",
90 " other{# days left}"
91 };
92 static const Pluralities IDS_REMAINING_MONTH = {
93 IDS_TIME_REMAINING_MONTHS, "one{# month left}", " other{# months left}"};
94 static const Pluralities IDS_REMAINING_YEAR = {
95 IDS_TIME_REMAINING_YEARS, "one{# year left}", " other{# years left}"};
96
97 static const Pluralities IDS_DURATION_SHORT_SEC = {
98 IDS_TIME_SECS,
99 "one{# sec}",
100 " other{# secs}"
101 };
102 static const Pluralities IDS_DURATION_SHORT_MIN = {
103 IDS_TIME_MINS,
104 "one{# min}",
105 " other{# mins}"
106 };
107
108 static const Pluralities IDS_LONG_SEC = {
109 IDS_TIME_LONG_SECS,
110 "one{# second}",
111 " other{# seconds}"
112 };
113 static const Pluralities IDS_LONG_MIN = {
114 IDS_TIME_LONG_MINS,
115 "one{# minute}",
116 " other{# minutes}"
117 };
118 static const Pluralities IDS_DURATION_HOUR = {
119 IDS_TIME_HOURS,
120 "one{# hour}",
121 " other{# hours}"
122 };
123 static const Pluralities IDS_DURATION_DAY = {
124 IDS_TIME_DAYS,
125 "one{# day}",
126 " other{# days}"
127 };
128 static const Pluralities IDS_DURATION_MONTH = {IDS_TIME_MONTHS, "one{# month}",
129 " other{# months}"};
130 static const Pluralities IDS_DURATION_YEAR = {IDS_TIME_YEARS, "one{# year}",
131 " other{# years}"};
132
133 static const Pluralities IDS_LONG_MIN_1ST = {
134 IDS_TIME_LONG_MINS_1ST,
135 "one{# minute and }",
136 " other{# minutes and }"
137 };
138 static const Pluralities IDS_LONG_SEC_2ND = {
139 IDS_TIME_LONG_SECS_2ND,
140 "one{# second}",
141 " other{# seconds}"
142 };
143 static const Pluralities IDS_DURATION_HOUR_1ST = {
144 IDS_TIME_HOURS_1ST,
145 "one{# hour and }",
146 " other{# hours and }"
147 };
148 static const Pluralities IDS_LONG_MIN_2ND = {
149 IDS_TIME_LONG_MINS_2ND,
150 "one{# minute}",
151 " other{# minutes}"
152 };
153 static const Pluralities IDS_DURATION_DAY_1ST = {
154 IDS_TIME_DAYS_1ST,
155 "one{# day and }",
156 " other{# days and }"
157 };
158 static const Pluralities IDS_DURATION_HOUR_2ND = {
159 IDS_TIME_HOURS_2ND,
160 "one{# hour}",
161 " other{# hours}"
162 };
163
164 namespace {
165
BuildPluralRules()166 std::unique_ptr<icu::PluralRules> BuildPluralRules() {
167 UErrorCode err = U_ZERO_ERROR;
168 std::unique_ptr<icu::PluralRules> rules(
169 icu::PluralRules::forLocale(icu::Locale::getDefault(), err));
170 if (U_FAILURE(err)) {
171 err = U_ZERO_ERROR;
172 icu::UnicodeString fallback_rules("one: n is 1", -1, US_INV);
173 rules.reset(icu::PluralRules::createRules(fallback_rules, err));
174 DCHECK(U_SUCCESS(err));
175 }
176 return rules;
177 }
178
FormatNumberInPlural(const icu::MessageFormat & format,int number,icu::UnicodeString * result,UErrorCode * err)179 void FormatNumberInPlural(const icu::MessageFormat& format, int number,
180 icu::UnicodeString* result, UErrorCode* err) {
181 if (U_FAILURE(*err)) return;
182 icu::Formattable formattable(number);
183 icu::FieldPosition ignore(icu::FieldPosition::DONT_CARE);
184 format.format(&formattable, 1, *result, ignore, *err);
185 DCHECK(U_SUCCESS(*err));
186 return;
187 }
188
189 } // namespace
190
Formatter(const Pluralities & sec_pluralities,const Pluralities & min_pluralities,const Pluralities & hour_pluralities,const Pluralities & day_pluralities,const Pluralities & month_pluralities,const Pluralities & year_pluralities)191 Formatter::Formatter(const Pluralities& sec_pluralities,
192 const Pluralities& min_pluralities,
193 const Pluralities& hour_pluralities,
194 const Pluralities& day_pluralities,
195 const Pluralities& month_pluralities,
196 const Pluralities& year_pluralities) {
197 simple_format_[UNIT_SEC] = InitFormat(sec_pluralities);
198 simple_format_[UNIT_MIN] = InitFormat(min_pluralities);
199 simple_format_[UNIT_HOUR] = InitFormat(hour_pluralities);
200 simple_format_[UNIT_DAY] = InitFormat(day_pluralities);
201 simple_format_[UNIT_MONTH] = InitFormat(month_pluralities);
202 simple_format_[UNIT_YEAR] = InitFormat(year_pluralities);
203 }
204
Formatter(const Pluralities & sec_pluralities,const Pluralities & min_pluralities,const Pluralities & hour_pluralities,const Pluralities & day_pluralities,const Pluralities & month_pluralities,const Pluralities & year_pluralities,const Pluralities & min_sec_pluralities1,const Pluralities & min_sec_pluralities2,const Pluralities & hour_min_pluralities1,const Pluralities & hour_min_pluralities2,const Pluralities & day_hour_pluralities1,const Pluralities & day_hour_pluralities2)205 Formatter::Formatter(const Pluralities& sec_pluralities,
206 const Pluralities& min_pluralities,
207 const Pluralities& hour_pluralities,
208 const Pluralities& day_pluralities,
209 const Pluralities& month_pluralities,
210 const Pluralities& year_pluralities,
211 const Pluralities& min_sec_pluralities1,
212 const Pluralities& min_sec_pluralities2,
213 const Pluralities& hour_min_pluralities1,
214 const Pluralities& hour_min_pluralities2,
215 const Pluralities& day_hour_pluralities1,
216 const Pluralities& day_hour_pluralities2) {
217 simple_format_[UNIT_SEC] = InitFormat(sec_pluralities);
218 simple_format_[UNIT_MIN] = InitFormat(min_pluralities);
219 simple_format_[UNIT_HOUR] = InitFormat(hour_pluralities);
220 simple_format_[UNIT_DAY] = InitFormat(day_pluralities);
221 simple_format_[UNIT_MONTH] = InitFormat(month_pluralities);
222 simple_format_[UNIT_YEAR] = InitFormat(year_pluralities);
223 detailed_format_[TWO_UNITS_MIN_SEC][0] = InitFormat(min_sec_pluralities1);
224 detailed_format_[TWO_UNITS_MIN_SEC][1] = InitFormat(min_sec_pluralities2);
225 detailed_format_[TWO_UNITS_HOUR_MIN][0] = InitFormat(hour_min_pluralities1);
226 detailed_format_[TWO_UNITS_HOUR_MIN][1] = InitFormat(hour_min_pluralities2);
227 detailed_format_[TWO_UNITS_DAY_HOUR][0] = InitFormat(day_hour_pluralities1);
228 detailed_format_[TWO_UNITS_DAY_HOUR][1] = InitFormat(day_hour_pluralities2);
229 }
230
Format(Unit unit,int value,icu::UnicodeString * formatted_string) const231 void Formatter::Format(Unit unit,
232 int value,
233 icu::UnicodeString* formatted_string) const {
234 DCHECK(simple_format_[unit]);
235 DCHECK(formatted_string->isEmpty() == TRUE);
236 UErrorCode error = U_ZERO_ERROR;
237 FormatNumberInPlural(*simple_format_[unit],
238 value, formatted_string, &error);
239 DCHECK(U_SUCCESS(error)) << "Error in icu::PluralFormat::format().";
240 return;
241 }
242
Format(TwoUnits units,int value_1,int value_2,icu::UnicodeString * formatted_string) const243 void Formatter::Format(TwoUnits units,
244 int value_1,
245 int value_2,
246 icu::UnicodeString* formatted_string) const {
247 DCHECK(detailed_format_[units][0])
248 << "Detailed() not implemented for your (format, length) combination!";
249 DCHECK(detailed_format_[units][1])
250 << "Detailed() not implemented for your (format, length) combination!";
251 DCHECK(formatted_string->isEmpty() == TRUE);
252 UErrorCode error = U_ZERO_ERROR;
253 FormatNumberInPlural(*detailed_format_[units][0], value_1,
254 formatted_string, &error);
255 DCHECK(U_SUCCESS(error));
256 FormatNumberInPlural(*detailed_format_[units][1], value_2,
257 formatted_string, &error);
258 DCHECK(U_SUCCESS(error));
259 return;
260 }
261
CreateFallbackFormat(const icu::PluralRules & rules,const Pluralities & pluralities) const262 std::unique_ptr<icu::MessageFormat> Formatter::CreateFallbackFormat(
263 const icu::PluralRules& rules,
264 const Pluralities& pluralities) const {
265 icu::UnicodeString pattern("{NUMBER, plural, ");
266 if (rules.isKeyword(UNICODE_STRING_SIMPLE("one")))
267 pattern += icu::UnicodeString(pluralities.fallback_one);
268 pattern += icu::UnicodeString(pluralities.fallback_other);
269 pattern.append(UChar(0x7du)); // "}" = U+007D
270
271 UErrorCode error = U_ZERO_ERROR;
272 std::unique_ptr<icu::MessageFormat> format(
273 new icu::MessageFormat(pattern, error));
274 DCHECK(U_SUCCESS(error));
275 return format;
276 }
277
InitFormat(const Pluralities & pluralities)278 std::unique_ptr<icu::MessageFormat> Formatter::InitFormat(
279 const Pluralities& pluralities) {
280 if (!formatter_force_fallback) {
281 base::string16 pattern = l10n_util::GetStringUTF16(pluralities.id);
282 UErrorCode error = U_ZERO_ERROR;
283 std::unique_ptr<icu::MessageFormat> format(new icu::MessageFormat(
284 icu::UnicodeString(FALSE, pattern.data(), pattern.length()), error));
285 DCHECK(U_SUCCESS(error));
286 if (format.get())
287 return format;
288 }
289
290 std::unique_ptr<icu::PluralRules> rules(BuildPluralRules());
291 return CreateFallbackFormat(*rules, pluralities);
292 }
293
Get(TimeFormat::Format format,TimeFormat::Length length) const294 const Formatter* FormatterContainer::Get(TimeFormat::Format format,
295 TimeFormat::Length length) const {
296 return formatter_[format][length].get();
297 }
298
FormatterContainer()299 FormatterContainer::FormatterContainer() {
300 Initialize();
301 }
302
~FormatterContainer()303 FormatterContainer::~FormatterContainer() {
304 }
305
Initialize()306 void FormatterContainer::Initialize() {
307 formatter_[TimeFormat::FORMAT_ELAPSED][TimeFormat::LENGTH_SHORT].reset(
308 new Formatter(IDS_ELAPSED_SHORT_SEC, IDS_ELAPSED_SHORT_MIN,
309 IDS_ELAPSED_HOUR, IDS_ELAPSED_DAY, IDS_ELAPSED_MONTH,
310 IDS_ELAPSED_YEAR));
311 formatter_[TimeFormat::FORMAT_ELAPSED][TimeFormat::LENGTH_LONG].reset(
312 new Formatter(IDS_ELAPSED_LONG_SEC, IDS_ELAPSED_LONG_MIN,
313 IDS_ELAPSED_HOUR, IDS_ELAPSED_DAY, IDS_ELAPSED_MONTH,
314 IDS_ELAPSED_YEAR));
315 formatter_[TimeFormat::FORMAT_REMAINING][TimeFormat::LENGTH_SHORT].reset(
316 new Formatter(IDS_REMAINING_SHORT_SEC, IDS_REMAINING_SHORT_MIN,
317 IDS_REMAINING_HOUR, IDS_REMAINING_DAY, IDS_REMAINING_MONTH,
318 IDS_REMAINING_YEAR));
319 formatter_[TimeFormat::FORMAT_REMAINING][TimeFormat::LENGTH_LONG].reset(
320 new Formatter(IDS_REMAINING_LONG_SEC, IDS_REMAINING_LONG_MIN,
321 IDS_REMAINING_HOUR, IDS_REMAINING_DAY, IDS_REMAINING_MONTH,
322 IDS_REMAINING_YEAR));
323 formatter_[TimeFormat::FORMAT_DURATION][TimeFormat::LENGTH_SHORT].reset(
324 new Formatter(IDS_DURATION_SHORT_SEC, IDS_DURATION_SHORT_MIN,
325 IDS_DURATION_HOUR, IDS_DURATION_DAY, IDS_DURATION_MONTH,
326 IDS_DURATION_YEAR));
327 formatter_[TimeFormat::FORMAT_DURATION][TimeFormat::LENGTH_LONG].reset(
328 new Formatter(IDS_LONG_SEC, IDS_LONG_MIN, IDS_DURATION_HOUR,
329 IDS_DURATION_DAY, IDS_DURATION_MONTH, IDS_DURATION_YEAR,
330 IDS_LONG_MIN_1ST, IDS_LONG_SEC_2ND, IDS_DURATION_HOUR_1ST,
331 IDS_LONG_MIN_2ND, IDS_DURATION_DAY_1ST,
332 IDS_DURATION_HOUR_2ND));
333 }
334
Shutdown()335 void FormatterContainer::Shutdown() {
336 for (int format = 0; format < TimeFormat::FORMAT_COUNT; ++format) {
337 for (int length = 0; length < TimeFormat::LENGTH_COUNT; ++length) {
338 formatter_[format][length].reset();
339 }
340 }
341 }
342
343 } // namespace ui
344