1 /* 2 * Copyright (C) 2012 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 #include "platform/text/LocaleWin.h" 33 34 #include <limits> 35 #include "platform/DateComponents.h" 36 #include "platform/Language.h" 37 #include "platform/LayoutTestSupport.h" 38 #include "platform/text/DateTimeFormat.h" 39 #include "wtf/CurrentTime.h" 40 #include "wtf/DateMath.h" 41 #include "wtf/HashMap.h" 42 #include "wtf/OwnPtr.h" 43 #include "wtf/PassOwnPtr.h" 44 #include "wtf/text/StringBuffer.h" 45 #include "wtf/text/StringBuilder.h" 46 #include "wtf/text/StringHash.h" 47 48 using namespace std; 49 50 namespace WebCore { 51 52 typedef LCID (WINAPI* LocaleNameToLCIDPtr)(LPCWSTR, DWORD); 53 typedef HashMap<String, LCID> NameToLCIDMap; 54 55 static String extractLanguageCode(const String& locale) 56 { 57 size_t dashPosition = locale.find('-'); 58 if (dashPosition == kNotFound) 59 return locale; 60 return locale.left(dashPosition); 61 } 62 63 static String removeLastComponent(const String& name) 64 { 65 size_t lastSeparator = name.reverseFind('-'); 66 if (lastSeparator == kNotFound) 67 return emptyString(); 68 return name.left(lastSeparator); 69 } 70 71 static void ensureNameToLCIDMap(NameToLCIDMap& map) 72 { 73 if (!map.isEmpty()) 74 return; 75 // http://www.microsoft.com/resources/msdn/goglobal/default.mspx 76 // We add only locales used in layout tests for now. 77 map.add("ar", 0x0001); 78 map.add("ar-eg", 0x0C01); 79 map.add("de", 0x0007); 80 map.add("de-de", 0x0407); 81 map.add("el", 0x0008); 82 map.add("el-gr", 0x0408); 83 map.add("en", 0x0009); 84 map.add("en-gb", 0x0809); 85 map.add("en-us", 0x0409); 86 map.add("fr", 0x000C); 87 map.add("fr-fr", 0x040C); 88 map.add("he", 0x000D); 89 map.add("he-il", 0x040D); 90 map.add("hi", 0x0039); 91 map.add("hi-in", 0x0439); 92 map.add("ja", 0x0011); 93 map.add("ja-jp", 0x0411); 94 map.add("ko", 0x0012); 95 map.add("ko-kr", 0x0412); 96 map.add("ru", 0x0019); 97 map.add("ru-ru", 0x0419); 98 map.add("zh-cn", 0x0804); 99 map.add("zh-tw", 0x0404); 100 } 101 102 // Fallback implementation of LocaleNameToLCID API. This is used for 103 // testing on Windows XP. 104 // FIXME: Remove this, ensureNameToLCIDMap, and removeLastComponent when we drop 105 // Windows XP support. 106 static LCID WINAPI convertLocaleNameToLCID(LPCWSTR name, DWORD) 107 { 108 if (!name || !name[0]) 109 return LOCALE_USER_DEFAULT; 110 DEFINE_STATIC_LOCAL(NameToLCIDMap, map, ()); 111 ensureNameToLCIDMap(map); 112 String localeName = String(name).replace('_', '-'); 113 localeName = localeName.lower(); 114 do { 115 NameToLCIDMap::const_iterator iterator = map.find(localeName); 116 if (iterator != map.end()) 117 return iterator->value; 118 localeName = removeLastComponent(localeName); 119 } while (!localeName.isEmpty()); 120 return LOCALE_USER_DEFAULT; 121 } 122 123 static LCID LCIDFromLocaleInternal(LCID userDefaultLCID, const String& userDefaultLanguageCode, LocaleNameToLCIDPtr localeNameToLCID, const String& locale) 124 { 125 String localeLanguageCode = extractLanguageCode(locale); 126 if (equalIgnoringCase(localeLanguageCode, userDefaultLanguageCode)) 127 return userDefaultLCID; 128 return localeNameToLCID(locale.charactersWithNullTermination().data(), 0); 129 } 130 131 static LCID LCIDFromLocale(const String& locale, bool defaultsForLocale) 132 { 133 // LocaleNameToLCID() is available since Windows Vista. 134 LocaleNameToLCIDPtr localeNameToLCID = reinterpret_cast<LocaleNameToLCIDPtr>(::GetProcAddress(::GetModuleHandle(L"kernel32"), "LocaleNameToLCID")); 135 if (!localeNameToLCID) 136 localeNameToLCID = convertLocaleNameToLCID; 137 138 // According to MSDN, 9 is enough for LOCALE_SISO639LANGNAME. 139 const size_t languageCodeBufferSize = 9; 140 WCHAR lowercaseLanguageCode[languageCodeBufferSize]; 141 ::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME | (defaultsForLocale ? LOCALE_NOUSEROVERRIDE : 0), lowercaseLanguageCode, languageCodeBufferSize); 142 String userDefaultLanguageCode = String(lowercaseLanguageCode); 143 144 LCID lcid = LCIDFromLocaleInternal(LOCALE_USER_DEFAULT, userDefaultLanguageCode, localeNameToLCID, locale); 145 if (!lcid) 146 lcid = LCIDFromLocaleInternal(LOCALE_USER_DEFAULT, userDefaultLanguageCode, localeNameToLCID, defaultLanguage()); 147 return lcid; 148 } 149 150 PassOwnPtr<Locale> Locale::create(const String& locale) 151 { 152 // Whether the default settings for the locale should be used, ignoring user overrides. 153 bool defaultsForLocale = isRunningLayoutTest(); 154 return LocaleWin::create(LCIDFromLocale(locale, defaultsForLocale), defaultsForLocale); 155 } 156 157 inline LocaleWin::LocaleWin(LCID lcid, bool defaultsForLocale) 158 : m_lcid(lcid) 159 , m_didInitializeNumberData(false) 160 , m_defaultsForLocale(defaultsForLocale) 161 { 162 DWORD value = 0; 163 getLocaleInfo(LOCALE_IFIRSTDAYOFWEEK | (defaultsForLocale ? LOCALE_NOUSEROVERRIDE : 0), value); 164 // 0:Monday, ..., 6:Sunday. 165 // We need 1 for Monday, 0 for Sunday. 166 m_firstDayOfWeek = (value + 1) % 7; 167 } 168 169 PassOwnPtr<LocaleWin> LocaleWin::create(LCID lcid, bool defaultsForLocale) 170 { 171 return adoptPtr(new LocaleWin(lcid, defaultsForLocale)); 172 } 173 174 LocaleWin::~LocaleWin() 175 { 176 } 177 178 String LocaleWin::getLocaleInfoString(LCTYPE type) 179 { 180 int bufferSizeWithNUL = ::GetLocaleInfo(m_lcid, type | (m_defaultsForLocale ? LOCALE_NOUSEROVERRIDE : 0), 0, 0); 181 if (bufferSizeWithNUL <= 0) 182 return String(); 183 StringBuffer<UChar> buffer(bufferSizeWithNUL); 184 ::GetLocaleInfo(m_lcid, type | (m_defaultsForLocale ? LOCALE_NOUSEROVERRIDE : 0), buffer.characters(), bufferSizeWithNUL); 185 buffer.shrink(bufferSizeWithNUL - 1); 186 return String::adopt(buffer); 187 } 188 189 void LocaleWin::getLocaleInfo(LCTYPE type, DWORD& result) 190 { 191 ::GetLocaleInfo(m_lcid, type | LOCALE_RETURN_NUMBER, reinterpret_cast<LPWSTR>(&result), sizeof(DWORD) / sizeof(TCHAR)); 192 } 193 194 void LocaleWin::ensureShortMonthLabels() 195 { 196 if (!m_shortMonthLabels.isEmpty()) 197 return; 198 const LCTYPE types[12] = { 199 LOCALE_SABBREVMONTHNAME1, 200 LOCALE_SABBREVMONTHNAME2, 201 LOCALE_SABBREVMONTHNAME3, 202 LOCALE_SABBREVMONTHNAME4, 203 LOCALE_SABBREVMONTHNAME5, 204 LOCALE_SABBREVMONTHNAME6, 205 LOCALE_SABBREVMONTHNAME7, 206 LOCALE_SABBREVMONTHNAME8, 207 LOCALE_SABBREVMONTHNAME9, 208 LOCALE_SABBREVMONTHNAME10, 209 LOCALE_SABBREVMONTHNAME11, 210 LOCALE_SABBREVMONTHNAME12, 211 }; 212 m_shortMonthLabels.reserveCapacity(WTF_ARRAY_LENGTH(types)); 213 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(types); ++i) { 214 m_shortMonthLabels.append(getLocaleInfoString(types[i])); 215 if (m_shortMonthLabels.last().isEmpty()) { 216 m_shortMonthLabels.shrink(0); 217 m_shortMonthLabels.reserveCapacity(WTF_ARRAY_LENGTH(WTF::monthName)); 218 for (unsigned m = 0; m < WTF_ARRAY_LENGTH(WTF::monthName); ++m) 219 m_shortMonthLabels.append(WTF::monthName[m]); 220 return; 221 } 222 } 223 } 224 225 // -------------------------------- Tokenized date format 226 227 static unsigned countContinuousLetters(const String& format, unsigned index) 228 { 229 unsigned count = 1; 230 UChar reference = format[index]; 231 while (index + 1 < format.length()) { 232 if (format[++index] != reference) 233 break; 234 ++count; 235 } 236 return count; 237 } 238 239 static void commitLiteralToken(StringBuilder& literalBuffer, StringBuilder& converted) 240 { 241 if (literalBuffer.length() <= 0) 242 return; 243 DateTimeFormat::quoteAndAppendLiteral(literalBuffer.toString(), converted); 244 literalBuffer.clear(); 245 } 246 247 // This function converts Windows date/time pattern format [1][2] into LDML date 248 // format pattern [3]. 249 // 250 // i.e. 251 // We set h, H, m, s, d, dd, M, or y as is. They have same meaning in both of 252 // Windows and LDML. 253 // We need to convert the following patterns: 254 // t -> a 255 // tt -> a 256 // ddd -> EEE 257 // dddd -> EEEE 258 // g -> G 259 // gg -> ignore 260 // 261 // [1] http://msdn.microsoft.com/en-us/library/dd317787(v=vs.85).aspx 262 // [2] http://msdn.microsoft.com/en-us/library/dd318148(v=vs.85).aspx 263 // [3] LDML http://unicode.org/reports/tr35/tr35-6.html#Date_Format_Patterns 264 static String convertWindowsDateTimeFormat(const String& format) 265 { 266 StringBuilder converted; 267 StringBuilder literalBuffer; 268 bool inQuote = false; 269 bool lastQuoteCanBeLiteral = false; 270 for (unsigned i = 0; i < format.length(); ++i) { 271 UChar ch = format[i]; 272 if (inQuote) { 273 if (ch == '\'') { 274 inQuote = false; 275 ASSERT(i); 276 if (lastQuoteCanBeLiteral && format[i - 1] == '\'') { 277 literalBuffer.append('\''); 278 lastQuoteCanBeLiteral = false; 279 } else { 280 lastQuoteCanBeLiteral = true; 281 } 282 } else { 283 literalBuffer.append(ch); 284 } 285 continue; 286 } 287 288 if (ch == '\'') { 289 inQuote = true; 290 if (lastQuoteCanBeLiteral && i > 0 && format[i - 1] == '\'') { 291 literalBuffer.append(ch); 292 lastQuoteCanBeLiteral = false; 293 } else { 294 lastQuoteCanBeLiteral = true; 295 } 296 } else if (isASCIIAlpha(ch)) { 297 commitLiteralToken(literalBuffer, converted); 298 unsigned symbolStart = i; 299 unsigned count = countContinuousLetters(format, i); 300 i += count - 1; 301 if (ch == 'h' || ch == 'H' || ch == 'm' || ch == 's' || ch == 'M' || ch == 'y') { 302 converted.append(format, symbolStart, count); 303 } else if (ch == 'd') { 304 if (count <= 2) 305 converted.append(format, symbolStart, count); 306 else if (count == 3) 307 converted.append("EEE"); 308 else 309 converted.append("EEEE"); 310 } else if (ch == 'g') { 311 if (count == 1) { 312 converted.append('G'); 313 } else { 314 // gg means imperial era in Windows. 315 // Just ignore it. 316 } 317 } else if (ch == 't') { 318 converted.append('a'); 319 } else { 320 literalBuffer.append(format, symbolStart, count); 321 } 322 } else { 323 literalBuffer.append(ch); 324 } 325 } 326 commitLiteralToken(literalBuffer, converted); 327 return converted.toString(); 328 } 329 330 void LocaleWin::ensureMonthLabels() 331 { 332 if (!m_monthLabels.isEmpty()) 333 return; 334 const LCTYPE types[12] = { 335 LOCALE_SMONTHNAME1, 336 LOCALE_SMONTHNAME2, 337 LOCALE_SMONTHNAME3, 338 LOCALE_SMONTHNAME4, 339 LOCALE_SMONTHNAME5, 340 LOCALE_SMONTHNAME6, 341 LOCALE_SMONTHNAME7, 342 LOCALE_SMONTHNAME8, 343 LOCALE_SMONTHNAME9, 344 LOCALE_SMONTHNAME10, 345 LOCALE_SMONTHNAME11, 346 LOCALE_SMONTHNAME12, 347 }; 348 m_monthLabels.reserveCapacity(WTF_ARRAY_LENGTH(types)); 349 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(types); ++i) { 350 m_monthLabels.append(getLocaleInfoString(types[i])); 351 if (m_monthLabels.last().isEmpty()) { 352 m_monthLabels.shrink(0); 353 m_monthLabels.reserveCapacity(WTF_ARRAY_LENGTH(WTF::monthFullName)); 354 for (unsigned m = 0; m < WTF_ARRAY_LENGTH(WTF::monthFullName); ++m) 355 m_monthLabels.append(WTF::monthFullName[m]); 356 return; 357 } 358 } 359 } 360 361 void LocaleWin::ensureWeekDayShortLabels() 362 { 363 if (!m_weekDayShortLabels.isEmpty()) 364 return; 365 const LCTYPE types[7] = { 366 LOCALE_SABBREVDAYNAME7, // Sunday 367 LOCALE_SABBREVDAYNAME1, // Monday 368 LOCALE_SABBREVDAYNAME2, 369 LOCALE_SABBREVDAYNAME3, 370 LOCALE_SABBREVDAYNAME4, 371 LOCALE_SABBREVDAYNAME5, 372 LOCALE_SABBREVDAYNAME6 373 }; 374 m_weekDayShortLabels.reserveCapacity(WTF_ARRAY_LENGTH(types)); 375 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(types); ++i) { 376 m_weekDayShortLabels.append(getLocaleInfoString(types[i])); 377 if (m_weekDayShortLabels.last().isEmpty()) { 378 m_weekDayShortLabels.shrink(0); 379 m_weekDayShortLabels.reserveCapacity(WTF_ARRAY_LENGTH(WTF::weekdayName)); 380 for (unsigned w = 0; w < WTF_ARRAY_LENGTH(WTF::weekdayName); ++w) { 381 // weekdayName starts with Monday. 382 m_weekDayShortLabels.append(WTF::weekdayName[(w + 6) % 7]); 383 } 384 return; 385 } 386 } 387 } 388 389 const Vector<String>& LocaleWin::monthLabels() 390 { 391 ensureMonthLabels(); 392 return m_monthLabels; 393 } 394 395 const Vector<String>& LocaleWin::weekDayShortLabels() 396 { 397 ensureWeekDayShortLabels(); 398 return m_weekDayShortLabels; 399 } 400 401 unsigned LocaleWin::firstDayOfWeek() 402 { 403 return m_firstDayOfWeek; 404 } 405 406 bool LocaleWin::isRTL() 407 { 408 WTF::Unicode::Direction dir = WTF::Unicode::direction(monthLabels()[0][0]); 409 return dir == WTF::Unicode::RightToLeft || dir == WTF::Unicode::RightToLeftArabic; 410 } 411 412 String LocaleWin::dateFormat() 413 { 414 if (m_dateFormat.isNull()) 415 m_dateFormat = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SSHORTDATE)); 416 return m_dateFormat; 417 } 418 419 String LocaleWin::dateFormat(const String& windowsFormat) 420 { 421 return convertWindowsDateTimeFormat(windowsFormat); 422 } 423 424 String LocaleWin::monthFormat() 425 { 426 if (m_monthFormat.isNull()) 427 m_monthFormat = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SYEARMONTH)); 428 return m_monthFormat; 429 } 430 431 String LocaleWin::shortMonthFormat() 432 { 433 if (m_shortMonthFormat.isNull()) 434 m_shortMonthFormat = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SYEARMONTH)).replace("MMMM", "MMM"); 435 return m_shortMonthFormat; 436 } 437 438 String LocaleWin::timeFormat() 439 { 440 if (m_timeFormatWithSeconds.isNull()) 441 m_timeFormatWithSeconds = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_STIMEFORMAT)); 442 return m_timeFormatWithSeconds; 443 } 444 445 String LocaleWin::shortTimeFormat() 446 { 447 if (!m_timeFormatWithoutSeconds.isNull()) 448 return m_timeFormatWithoutSeconds; 449 String format = getLocaleInfoString(LOCALE_SSHORTTIME); 450 // Vista or older Windows doesn't support LOCALE_SSHORTTIME. 451 if (format.isEmpty()) { 452 format = getLocaleInfoString(LOCALE_STIMEFORMAT); 453 StringBuilder builder; 454 builder.append(getLocaleInfoString(LOCALE_STIME)); 455 builder.append("ss"); 456 size_t pos = format.reverseFind(builder.toString()); 457 if (pos != kNotFound) 458 format.remove(pos, builder.length()); 459 } 460 m_timeFormatWithoutSeconds = convertWindowsDateTimeFormat(format); 461 return m_timeFormatWithoutSeconds; 462 } 463 464 String LocaleWin::dateTimeFormatWithSeconds() 465 { 466 if (!m_dateTimeFormatWithSeconds.isNull()) 467 return m_dateTimeFormatWithSeconds; 468 StringBuilder builder; 469 builder.append(dateFormat()); 470 builder.append(' '); 471 builder.append(timeFormat()); 472 m_dateTimeFormatWithSeconds = builder.toString(); 473 return m_dateTimeFormatWithSeconds; 474 } 475 476 String LocaleWin::dateTimeFormatWithoutSeconds() 477 { 478 if (!m_dateTimeFormatWithoutSeconds.isNull()) 479 return m_dateTimeFormatWithoutSeconds; 480 StringBuilder builder; 481 builder.append(dateFormat()); 482 builder.append(' '); 483 builder.append(shortTimeFormat()); 484 m_dateTimeFormatWithoutSeconds = builder.toString(); 485 return m_dateTimeFormatWithoutSeconds; 486 } 487 488 const Vector<String>& LocaleWin::shortMonthLabels() 489 { 490 ensureShortMonthLabels(); 491 return m_shortMonthLabels; 492 } 493 494 const Vector<String>& LocaleWin::standAloneMonthLabels() 495 { 496 // Windows doesn't provide a way to get stand-alone month labels. 497 return monthLabels(); 498 } 499 500 const Vector<String>& LocaleWin::shortStandAloneMonthLabels() 501 { 502 // Windows doesn't provide a way to get stand-alone month labels. 503 return shortMonthLabels(); 504 } 505 506 const Vector<String>& LocaleWin::timeAMPMLabels() 507 { 508 if (m_timeAMPMLabels.isEmpty()) { 509 m_timeAMPMLabels.append(getLocaleInfoString(LOCALE_S1159)); 510 m_timeAMPMLabels.append(getLocaleInfoString(LOCALE_S2359)); 511 } 512 return m_timeAMPMLabels; 513 } 514 515 void LocaleWin::initializeLocaleData() 516 { 517 if (m_didInitializeNumberData) 518 return; 519 520 Vector<String, DecimalSymbolsSize> symbols; 521 enum DigitSubstitution { 522 DigitSubstitutionContext = 0, 523 DigitSubstitution0to9 = 1, 524 DigitSubstitutionNative = 2, 525 }; 526 DWORD digitSubstitution = DigitSubstitution0to9; 527 getLocaleInfo(LOCALE_IDIGITSUBSTITUTION, digitSubstitution); 528 if (digitSubstitution == DigitSubstitution0to9) { 529 symbols.append("0"); 530 symbols.append("1"); 531 symbols.append("2"); 532 symbols.append("3"); 533 symbols.append("4"); 534 symbols.append("5"); 535 symbols.append("6"); 536 symbols.append("7"); 537 symbols.append("8"); 538 symbols.append("9"); 539 } else { 540 String digits = getLocaleInfoString(LOCALE_SNATIVEDIGITS); 541 ASSERT(digits.length() >= 10); 542 for (unsigned i = 0; i < 10; ++i) 543 symbols.append(digits.substring(i, 1)); 544 } 545 ASSERT(symbols.size() == DecimalSeparatorIndex); 546 symbols.append(getLocaleInfoString(LOCALE_SDECIMAL)); 547 ASSERT(symbols.size() == GroupSeparatorIndex); 548 symbols.append(getLocaleInfoString(LOCALE_STHOUSAND)); 549 ASSERT(symbols.size() == DecimalSymbolsSize); 550 551 String negativeSign = getLocaleInfoString(LOCALE_SNEGATIVESIGN); 552 enum NegativeFormat { 553 NegativeFormatParenthesis = 0, 554 NegativeFormatSignPrefix = 1, 555 NegativeFormatSignSpacePrefix = 2, 556 NegativeFormatSignSuffix = 3, 557 NegativeFormatSpaceSignSuffix = 4, 558 }; 559 DWORD negativeFormat = NegativeFormatSignPrefix; 560 getLocaleInfo(LOCALE_INEGNUMBER, negativeFormat); 561 String negativePrefix = emptyString(); 562 String negativeSuffix = emptyString(); 563 switch (negativeFormat) { 564 case NegativeFormatParenthesis: 565 negativePrefix = "("; 566 negativeSuffix = ")"; 567 break; 568 case NegativeFormatSignSpacePrefix: 569 negativePrefix = negativeSign + " "; 570 break; 571 case NegativeFormatSignSuffix: 572 negativeSuffix = negativeSign; 573 break; 574 case NegativeFormatSpaceSignSuffix: 575 negativeSuffix = " " + negativeSign; 576 break; 577 case NegativeFormatSignPrefix: // Fall through. 578 default: 579 negativePrefix = negativeSign; 580 break; 581 } 582 m_didInitializeNumberData = true; 583 setLocaleData(symbols, emptyString(), emptyString(), negativePrefix, negativeSuffix); 584 } 585 586 } 587