1/* 2 * Copyright (C) 2010, 2013 Apple 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 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27#include "Language.h" 28 29#include <wtf/HashMap.h> 30#include <wtf/RetainPtr.h> 31#include <wtf/text/WTFString.h> 32 33#if PLATFORM(MAC) 34#include <CoreFoundation/CoreFoundation.h> 35#endif 36 37namespace WebCore { 38 39typedef HashMap<void*, LanguageChangeObserverFunction> ObserverMap; 40static ObserverMap& observerMap() 41{ 42 DEFINE_STATIC_LOCAL(ObserverMap, map, ()); 43 return map; 44} 45 46void addLanguageChangeObserver(void* context, LanguageChangeObserverFunction customObserver) 47{ 48 observerMap().set(context, customObserver); 49} 50 51void removeLanguageChangeObserver(void* context) 52{ 53 ASSERT(observerMap().contains(context)); 54 observerMap().remove(context); 55} 56 57void languageDidChange() 58{ 59 ObserverMap::iterator end = observerMap().end(); 60 for (ObserverMap::iterator iter = observerMap().begin(); iter != end; ++iter) 61 iter->value(iter->key); 62} 63 64String defaultLanguage() 65{ 66 Vector<String> languages = userPreferredLanguages(); 67 if (languages.size()) 68 return languages[0]; 69 70 return emptyString(); 71} 72 73static Vector<String>& preferredLanguagesOverride() 74{ 75 DEFINE_STATIC_LOCAL(Vector<String>, override, ()); 76 return override; 77} 78 79Vector<String> userPreferredLanguagesOverride() 80{ 81 return preferredLanguagesOverride(); 82} 83 84void overrideUserPreferredLanguages(const Vector<String>& override) 85{ 86 preferredLanguagesOverride() = override; 87} 88 89Vector<String> userPreferredLanguages() 90{ 91 Vector<String>& override = preferredLanguagesOverride(); 92 if (!override.isEmpty()) 93 return override; 94 95 return platformUserPreferredLanguages(); 96} 97 98static String canonicalLanguageIdentifier(const String& languageCode) 99{ 100 String lowercaseLanguageCode = languageCode.lower(); 101 102 if (lowercaseLanguageCode.length() >= 3 && lowercaseLanguageCode[2] == '_') 103 lowercaseLanguageCode.replace(2, 1, "-"); 104 105 return lowercaseLanguageCode; 106} 107 108size_t indexOfBestMatchingLanguageInList(const String& language, const Vector<String>& languageList) 109{ 110 String languageWithoutLocaleMatch; 111 String languageMatchButNotLocale; 112 size_t languageWithoutLocaleMatchIndex = 0; 113 size_t languageMatchButNotLocaleMatchIndex = 0; 114 bool canMatchLanguageOnly = (language.length() == 2 || (language.length() >= 3 && language[2] == '-')); 115 116 for (size_t i = 0; i < languageList.size(); ++i) { 117 String canonicalizedLanguageFromList = canonicalLanguageIdentifier(languageList[i]); 118 119 if (language == canonicalizedLanguageFromList) 120 return i; 121 122 if (canMatchLanguageOnly && canonicalizedLanguageFromList.length() >= 2) { 123 if (language[0] == canonicalizedLanguageFromList[0] && language[1] == canonicalizedLanguageFromList[1]) { 124 if (!languageWithoutLocaleMatch.length() && canonicalizedLanguageFromList.length() == 2) { 125 languageWithoutLocaleMatch = languageList[i]; 126 languageWithoutLocaleMatchIndex = i; 127 } 128 if (!languageMatchButNotLocale.length() && canonicalizedLanguageFromList.length() >= 3) { 129 languageMatchButNotLocale = languageList[i]; 130 languageMatchButNotLocaleMatchIndex = i; 131 } 132 } 133 } 134 } 135 136 // If we have both a language-only match and a languge-but-not-locale match, return the 137 // languge-only match as is considered a "better" match. For example, if the list 138 // provided has both "en-GB" and "en" and the user prefers "en-US" we will return "en". 139 if (languageWithoutLocaleMatch.length()) 140 return languageWithoutLocaleMatchIndex; 141 142 if (languageMatchButNotLocale.length()) 143 return languageMatchButNotLocaleMatchIndex; 144 145 return languageList.size(); 146} 147 148String displayNameForLanguageLocale(const String& localeName) 149{ 150#if PLATFORM(MAC) 151 if (!localeName.isNull() && !localeName.isEmpty()) { 152 RetainPtr<CFLocaleRef> currentLocale = adoptCF(CFLocaleCopyCurrent()); 153 return CFLocaleCopyDisplayNameForPropertyValue(currentLocale.get(), kCFLocaleIdentifier, localeName.createCFString().get()); 154 } 155#endif 156 return localeName; 157} 158 159} 160