blob: 1764032b5e6705742bb8052db84b66b0ec9a2a43 [file] [log] [blame]
/* CFBundle_Strings.c
Copyright (c) 1999-2016, Apple Inc. and the Swift project authors
Portions Copyright (c) 2014-2016 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception
See http://swift.org/LICENSE.txt for license information
See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
Responsibility: Tony Parker
*/
#include "CFBundle_Internal.h"
#if DEPLOYMENT_TARGET_MACOSX
#endif
#include <CoreFoundation/CFPreferences.h>
#include <CoreFoundation/CFURLAccess.h>
#pragma mark -
#pragma mark Localized Strings
typedef struct {
CFDictionaryRef localizedFormatStringsTable;
CFMutableDictionaryRef result;
} VariableWidthStringContext;
CF_EXPORT CFStringRef CFBundleCopyLocalizedString(CFBundleRef bundle, CFStringRef key, CFStringRef value, CFStringRef tableName) {
return CFBundleCopyLocalizedStringForLocalization(bundle, key, value, tableName, NULL);
}
static CFStringRef _copyStringFromTable(CFBundleRef bundle, CFStringRef tableName, CFStringRef key, CFStringRef localizationName) {
// Check the cache first. If it's not there, populate the cache and check again.
__CFLock(&bundle->_lock);
// Only consult the cache when a specific localization has not been requested. We only cache results for the preferred language as determined by normal bundle lookup rules.
if (!localizationName && bundle->_stringTable) {
CFDictionaryRef stringTable = (CFDictionaryRef)CFDictionaryGetValue(bundle->_stringTable, tableName);
if (stringTable) {
CFStringRef result = CFDictionaryGetValue(stringTable, key);
if (result) {
CFRetain(result);
}
__CFUnlock(&bundle->_lock);
return result;
}
}
// Not in the local cache, so load the table. Unlock so we don't hold the lock across file system access.
__CFUnlock(&bundle->_lock);
CFDictionaryRef stringsTable = NULL;
CFURLRef stringsTableURL = NULL;
CFURLRef stringsDictTableURL = NULL;
// Find the resource URL.
if (localizationName) {
stringsTableURL = CFBundleCopyResourceURLForLocalization(bundle, tableName, _CFBundleStringTableType, NULL, localizationName);
stringsDictTableURL = CFBundleCopyResourceURLForLocalization(bundle, tableName, _CFBundleStringDictTableType, NULL, localizationName);
} else {
stringsTableURL = CFBundleCopyResourceURL(bundle, tableName, _CFBundleStringTableType, NULL);
stringsDictTableURL = CFBundleCopyResourceURL(bundle, tableName, _CFBundleStringDictTableType, NULL);
}
// Next, look on disk for the regular strings file.
if (!stringsTable && stringsTableURL) {
CFDataRef tableData = _CFDataCreateFromURL(stringsTableURL, NULL);
if (tableData) {
CFErrorRef error = NULL;
stringsTable = (CFDictionaryRef)CFPropertyListCreateWithData(CFGetAllocator(bundle), tableData, kCFPropertyListImmutable, NULL, &error);
CFRelease(tableData);
if (stringsTable && CFDictionaryGetTypeID() != CFGetTypeID(stringsTable)) {
os_log_error(_CFBundleLocalizedStringLogger(), "Unable to load .strings file: %@ / %@: Top-level object was not a dictionary", bundle, tableName);
CFRelease(stringsTable);
stringsTable = NULL;
} else if (!stringsTable && error) {
os_log_error(_CFBundleLocalizedStringLogger(), "Unable to load .strings file: %@ / %@: %@", bundle, tableName, error);
CFRelease(error);
error = NULL;
}
}
}
// Check for a .stringsdict file.
if (stringsDictTableURL) {
CFDataRef tableData = _CFDataCreateFromURL(stringsDictTableURL, NULL);
if (tableData) {
CFErrorRef error = NULL;
CFDictionaryRef stringsDictTable = (CFDictionaryRef)CFPropertyListCreateWithData(CFGetAllocator(bundle), tableData, kCFPropertyListImmutable, NULL, &error);
CFRelease(tableData);
if (stringsDictTable && CFDictionaryGetTypeID() != CFGetTypeID(stringsDictTable)) {
os_log_error(_CFBundleLocalizedStringLogger(), "Unable to load .stringsdict file: %@ / %@: Top-level object was not a dictionary", bundle, tableName);
CFRelease(stringsDictTable);
stringsDictTable = NULL;
} else if (!stringsDictTable && error) {
os_log_error(_CFBundleLocalizedStringLogger(), "Unable to load .stringsdict file: %@ / %@: %@", bundle, tableName, error);
CFRelease(error);
error = NULL;
}
// Post-process the strings table
if (stringsDictTable) {
CFMutableDictionaryRef mutableStringsDictTable;
if (stringsTable) {
// Use the strings table as base content for the stringsdict
mutableStringsDictTable = CFDictionaryCreateMutableCopy(NULL, 0, stringsTable);
} else {
// Start from scratch with the stringsdict
mutableStringsDictTable = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
CFRelease(stringsDictTable);
if (stringsTable) CFRelease(stringsTable);
stringsTable = mutableStringsDictTable;
}
}
}
if (stringsTableURL) CFRelease(stringsTableURL);
if (stringsDictTableURL) CFRelease(stringsDictTableURL);
// Last resort: create an empty table
if (!stringsTable) {
os_log_debug(_CFBundleLocalizedStringLogger(), "Hit last resort and creating empty strings table");
stringsTable = CFDictionaryCreate(CFGetAllocator(bundle), NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
// Insert the result into our local cache
if ((!CFStringHasSuffix(tableName, CFSTR(".nocache")) || !_CFExecutableLinkedOnOrAfter(CFSystemVersionLeopard)) && localizationName == NULL) {
// Take lock again, because this we will unlock after getting the value out of the table.
__CFLock(&bundle->_lock);
if (!bundle->_stringTable) bundle->_stringTable = CFDictionaryCreateMutable(CFGetAllocator(bundle), 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
// If another thread beat us to setting this tableName, then we'll just replace it here.
CFDictionarySetValue(bundle->_stringTable, tableName, stringsTable);
} else {
// Take lock again, because this we will unlock after getting the value out of the table.
__CFLock(&bundle->_lock);
}
// Finally, fetch the result from the table
CFStringRef result = CFDictionaryGetValue(stringsTable, key);
if (result) {
CFRetain(result);
}
__CFUnlock(&bundle->_lock);
CFRelease(stringsTable);
return result;
}
CF_EXPORT CFStringRef CFBundleCopyLocalizedStringForLocalization(CFBundleRef bundle, CFStringRef key, CFStringRef value, CFStringRef tableName, CFStringRef localizationName) {
if (!key) { return (value ? (CFStringRef)CFRetain(value) : (CFStringRef)CFRetain(CFSTR(""))); }
// Make sure to check the mixed localizations key early -- if the main bundle has not yet been cached, then we need to create the cache of the Info.plist before we start asking for resources (11172381)
(void)CFBundleAllowMixedLocalizations();
if (!tableName || CFEqual(tableName, CFSTR(""))) tableName = _CFBundleDefaultStringTableName;
CFStringRef result = _copyStringFromTable(bundle, tableName, key, localizationName);
if (!result) {
if (!value) {
result = (CFStringRef)CFRetain(key);
} else if (CFEqual(value, CFSTR(""))) {
result = (CFStringRef)CFRetain(key);
} else {
result = (CFStringRef)CFRetain(value);
}
static Boolean capitalize = false;
if (capitalize) {
CFMutableStringRef capitalizedResult = CFStringCreateMutableCopy(kCFAllocatorSystemDefault, 0, result);
os_log_error(_CFBundleLocalizedStringLogger(), "ERROR: %@ not found in table %@ of bundle %@", key, tableName, bundle);
CFStringUppercase(capitalizedResult, NULL);
CFRelease(result);
result = capitalizedResult;
}
}
os_log_debug(_CFBundleLocalizedStringLogger(), "Bundle: %{private}@, key: %{public}@, value: %{public}@, table: %{public}@, localizationName: %{public}@, result: %{public}@", bundle, key, value, tableName, localizationName, result);
return result;
}