2 * Copyright (C) 2003, 2005, 2006, 2010, 2011, 2016 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 #import "BlockExceptions.h"
30 #import "CFBundleSPI.h"
31 #import "WebCoreNSStringExtras.h"
33 #import <unicode/uloc.h>
34 #import <wtf/Assertions.h>
36 #import <wtf/NeverDestroyed.h>
37 #import <wtf/RetainPtr.h>
38 #import <wtf/text/WTFString.h>
42 static StaticLock preferredLanguagesMutex;
44 static Vector<String>& preferredLanguages()
46 static NeverDestroyed<Vector<String>> languages;
52 @interface WebLanguageChangeObserver : NSObject
55 @implementation WebLanguageChangeObserver
57 + (void)languagePreferencesDidChange:(NSNotification *)notification
59 UNUSED_PARAM(notification);
62 std::lock_guard<StaticLock> lock(WebCore::preferredLanguagesMutex);
63 WebCore::preferredLanguages().clear();
66 WebCore::languageDidChange();
73 static String httpStyleLanguageCode(NSString *language, NSString *country)
78 CFStringEncoding stringEncoding;
80 bool languageDidSpecifyExplicitVariant = [language rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"-_"]].location != NSNotFound;
82 // FIXME: This transformation is very wrong:
83 // 1. There is no reason why CFBundle localization names would be at all related to language names as used on the Web.
84 // 2. Script Manager codes cannot represent all languages that are now supported by the platform, so the conversion is lossy.
85 // 3. This should probably match what is sent by the network layer as Accept-Language, but currently, that's implemented separately.
86 CFBundleGetLocalizationInfoForLocalization((CFStringRef)language, &languageCode, ®ionCode, &scriptCode, &stringEncoding);
87 RetainPtr<CFStringRef> preferredLanguageCode = adoptCF(CFBundleCopyLocalizationForLocalizationInfo(languageCode, regionCode, scriptCode, stringEncoding));
88 if (preferredLanguageCode)
89 language = (NSString *)preferredLanguageCode.get();
91 // Make the string lowercase.
92 NSString *lowercaseLanguageCode = [language lowercaseString];
93 NSString *lowercaseCountryCode = [country lowercaseString];
95 // If we see a "_" after a 2-letter language code:
96 // If the country is valid and the language did not specify a variant, replace the "_" and
97 // whatever comes after it with "-" followed by the country code.
98 // Otherwise, replace the "_" with a "-" and use whatever country
99 // CFBundleCopyLocalizationForLocalizationInfo() returned.
100 if ([lowercaseLanguageCode length] >= 3 && [lowercaseLanguageCode characterAtIndex:2] == '_') {
101 if (country && !languageDidSpecifyExplicitVariant)
102 return [NSString stringWithFormat:@"%@-%@", [lowercaseLanguageCode substringWithRange:NSMakeRange(0, 2)], lowercaseCountryCode];
104 // Fall back to older behavior, which used the original language-based code but just changed
106 RetainPtr<NSMutableString> mutableLanguageCode = adoptNS([lowercaseLanguageCode mutableCopy]);
107 [mutableLanguageCode.get() replaceCharactersInRange:NSMakeRange(2, 1) withString:@"-"];
108 return mutableLanguageCode.get();
111 return lowercaseLanguageCode;
114 static bool isValidICUCountryCode(NSString* countryCode)
118 const char* const* countries = uloc_getISOCountries();
119 const char* countryUTF8 = [countryCode UTF8String];
120 for (unsigned i = 0; countries[i]; ++i) {
121 const char* possibleCountry = countries[i];
122 if (!strcmp(countryUTF8, possibleCountry))
128 Vector<String> platformUserPreferredLanguages()
131 static dispatch_once_t onceToken;
132 dispatch_once(&onceToken, ^{
133 [[NSDistributedNotificationCenter defaultCenter] addObserver:[WebLanguageChangeObserver self] selector:@selector(languagePreferencesDidChange:) name:@"AppleLanguagePreferencesChangedNotification" object:nil];
137 BEGIN_BLOCK_OBJC_EXCEPTIONS;
139 std::lock_guard<StaticLock> lock(preferredLanguagesMutex);
140 Vector<String>& userPreferredLanguages = preferredLanguages();
142 if (userPreferredLanguages.isEmpty()) {
143 RetainPtr<CFLocaleRef> locale = adoptCF(CFLocaleCopyCurrent());
144 NSString *countryCode = (NSString *)CFLocaleGetValue(locale.get(), kCFLocaleCountryCode);
146 if (!isValidICUCountryCode(countryCode))
149 RetainPtr<CFArrayRef> languages = adoptCF(CFLocaleCopyPreferredLanguages());
150 CFIndex languageCount = CFArrayGetCount(languages.get());
152 userPreferredLanguages.append("en");
154 for (CFIndex i = 0; i < languageCount; i++)
155 userPreferredLanguages.append(httpStyleLanguageCode((NSString *)CFArrayGetValueAtIndex(languages.get(), i), countryCode));
159 Vector<String> userPreferredLanguagesCopy;
160 userPreferredLanguagesCopy.reserveInitialCapacity(userPreferredLanguages.size());
162 for (auto& language : userPreferredLanguages)
163 userPreferredLanguagesCopy.uncheckedAppend(language.isolatedCopy());
165 return userPreferredLanguagesCopy;
167 END_BLOCK_OBJC_EXCEPTIONS;
169 return Vector<String>();