-
-
Save jkaunert/51874f40a67f0e41d06c38d852fb7cc3 to your computer and use it in GitHub Desktop.
Locales
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
====================================================== | |
THIS CODE IS FOR EDUCATIONAL PURPOSES ONLY. | |
I'M NOT RESPONSIBLE IF YOU SHIP THIS AND IT BLOWS UP IN YOUR FACE. | |
IF IT DOES AND YOU COMPLAIN TO ME I WILL LAUGH AT YOU. | |
====================================================== | |
This code is based on the following premise: | |
• You want to show things in a particular locale | |
• You want to honor any overrides the user has set | |
iOS will only format dates using the +[NSLocale currentLocale] if the app is localized to support that locale. | |
That means that if your app is running on: | |
• a device set to a language your app does not support AND | |
• the user has custom overrides for 12/24 hour, date/time formats, etc | |
THEN: | |
• the user will see dates formatted in another language, and WITHOUT their overrides. | |
This is because you end up getting a locale that's just the "default" locale for a particular language, | |
with no overrides applied. | |
Enter this code. | |
This code attempts to work around this (admittedly rather edge case) scenario. | |
The overrides on a locale are stored inside the "_prefs" field. For the current locale, | |
this is basically copied out of NSUserDefaults and is how the current locale knows about the | |
12/24 hour override, among other things. | |
For custom locales, this is NULL. And there is no way to change it. | |
Also working against us is the fact that _prefs IS NOT AN OBJC IVAR FIELD. NSLocale is toll-free | |
bridged to CFLocaleRef, which means we're really dealing with pointers to C structs, and the fields | |
on those structs are not described to the Objective-C runtime. So the Runtime doesn't even help us. | |
So instead, we get to drop down to the raw memory layout and fudge things around. | |
Please don't use this in shipping code. | |
*/ | |
@interface NSLocale (Voodoo) | |
// hide all the nastiness behind a nice, pleasant method | |
+ (NSLocale *)localeIncludingOverridesWithLocaleIdentifier:(NSString *)localeIdentifier; | |
@end | |
// this is the (rough) layout of a CFLocaleRef | |
// I figured this out based on: | |
// - https://github.com/apple/swift-corelibs-foundation/blob/155f1ce1965effe55289477507a6f9fbdc8fe333/CoreFoundation/Locale.subproj/CFLocale.c#L144-L151 | |
// - too much time in the debugger | |
struct __DDLocale { | |
void *_base; // The CF version of an "isa" pointer | |
void *_field1; // maybe the locale identifier? | |
void *_field2; // maybe a cache | |
void *_field3; // no idea | |
CFDictionaryRef _prefs; // The Secret Sauce | |
void *_lock; // maybe some thread safety thing | |
Boolean _nullLocale; // who knows | |
}; | |
@implementation NSLocale (Voodoo) | |
+ (NSLocale *)localeIncludingOverridesWithLocaleIdentifier:(NSString *)localeIdentifier { | |
// First, rip apart the locale identifier in to its components | |
// Why? because if you create a locale w/ an identifier, and another locale instance | |
// already exists that has the same identifier, you'll just get a pointer back | |
// to the pre-existing object. For this class, that's probably what you want | |
// most of the time. For this scenario, it's not. | |
NSMutableDictionary *components = [[NSLocale componentsFromLocaleIdentifier:localeIdentifier] mutableCopy]; | |
// So instead we'll "force" a new instance of a locale by creating a never-before-seen | |
// locale identifier, thanks to the improbable magic of NSUUID | |
[components setObject:[[NSUUID UUID] UUIDString] forKey:@"custom"]; | |
// Turn the components back into a locale identifier. It's now "guaranteed" | |
// to be unique | |
NSString *newID = [NSLocale localeIdentifierFromComponents:components]; | |
// Construct a brand-spanking-new NSLocale | |
NSLocale *copy = [[NSLocale alloc] initWithLocaleIdentifier:newID]; | |
// If, for some reason, it failed, early return | |
// This is because we're about to do pointer magic, and doing pointer | |
// magic with a NULL pointer is a great way to crash. Let's not crash. | |
if (copy == nil) { return nil; } | |
// Cast (lie to the compiler) that these pointers are actually pointers | |
// to our struct definition that we worked out before | |
struct __DDLocale* this = (struct __DDLocale *)[NSLocale currentLocale]; | |
struct __DDLocale* that = (struct __DDLocale *)copy; | |
// Copy over the dictionary of overrides from the +currentLocale | |
// to our new copy. This is how our copy will get to know about the | |
// overrides as well | |
that->_prefs = CFDictionaryCreateMutableCopy(NULL, 0, this->_prefs); | |
// Share And Enjoy. | |
return copy; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment