-
Notifications
You must be signed in to change notification settings - Fork 806
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix NSLocalizedString and Add Localization Tests #2824
Conversation
@@ -531,6 +536,10 @@ CF_PRIVATE CFArrayRef _CFBundleCopyUserLanguages() { | |||
static CFArrayRef _CFBundleUserLanguages = NULL; | |||
static dispatch_once_t once = 0; | |||
dispatch_once(&once, ^{ | |||
// WINOBJC: __CFAppleLanguages does not exist on Windows | |||
#if DEPLOYMENT_TARGET_WINDOWS | |||
_CFBundleUserLanguages = EnumerateUserPreferredLanguages(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per offline suggestion, I'd prefer we use CFLocaleGetPreferredUserLanguages
or whatever it's called. That way we don't need to use our WinRT Enumerate
directly everywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: should we be transforming this list from -
to _
on creation? it could save us the step of converting the -
to a _
for every lookup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could do it inside Enumerate if CF prefers _
locale names
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Windows locales are set up to use -
. If somebody is setting up localization, they may think to use the natural windows syntax in their bundle names. I think it makes sense to support both -
and _
in that case.
@@ -663,8 +673,66 @@ static CFStringRef _CFBundleCopyLanguageFoundInLocalizations(CFArrayRef localiza | |||
return NULL; | |||
} | |||
|
|||
// WINOBJC: Helper functions for workaround in _CFBundleCreateMutableArrayOfFallbackLanguages | |||
static CFStringRef _copyStringTruncated(CFStringRef localization, CFRange cutoff) { | |||
CFMutableStringRef truncatedString = CFStringCreateMutableCopy(NULL, 0, localization); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CFStringCreateWithSubstring
// Given a list of localizations (e.g., provided as argument to API, or present as .lproj directories), return a mutable array of localizations in preferred order. Returns NULL if nothing is found. | ||
static CFMutableArrayRef _CFBundleCreateMutableArrayOfFallbackLanguages(CFArrayRef availableLocalizations, CFArrayRef preferredLocalizations) { | ||
// WINOBJC: The API that performs the work described below does not exist in our 3rd party libraries |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: add note that ualoc_
is an apple-specific ICU API suite
// We know the user languages are in preferred order, add to this list in this order | ||
// Prefer the full language locale, attempt to convert any hyphens to underscores as | ||
// Windows language settings are retrieved with hyphens while underscores are commonly used for localization. | ||
// Finally, attempt to truncate any underscores from the language to find a base localization. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: give examples of these. "The user's preferred language list comes in as en-US and de-DE, ..."
|
||
for (CFIndex i = 0, count = CFArrayGetCount(preferredLocalizations); i < count; i++) { | ||
CFStringRef preferredLocalization = (CFStringRef)CFArrayGetValueAtIndex(preferredLocalizations, i); | ||
for (CFIndex j = 0, count = CFArrayGetCount(availableLocalizations); j < count; j++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these multiple scope-nested counts scare me
NSString* helloWorldInGerman = NSLocalizedStringFromTableInBundle(helloWorld, nil, bundle, nil); | ||
NSString* codingInChinese = NSLocalizedStringFromTableInBundle(coding, nil, bundle, nil); | ||
ASSERT_OBJCEQ(@"Hallo Welt", helloWorldInGerman); | ||
// Cannot use OBJCEQ as these unicode characters will cause a crash. isEqualToString is a code point literal comparison. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm sorry? what? a crash?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, unicode characters in ASSERT_OBJCEQ was just like... ¯_(ツ)_/¯ crash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compile time? Runtime? Either way, please file it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Run time. Will do!
NSString* _subDirectory = @"en.lproj"; | ||
NSString* localizationResourceName = @"Localizable.strings"; | ||
// Create a unique test directory | ||
NSString* tempDir = [[@"./tmp_TestFoundation" stringByAppendingString:[NSUUID UUID].UUIDString] stringByAppendingString:@"/"]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agh stringByAppendingPathComponent:
ASSERT_EQ(nil, error); | ||
|
||
// Make a flat bundle in the playground | ||
NSString* bundlePath = [tempDir stringByAppendingString:_bundleName]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
[[NSFileManager defaultManager] createDirectoryAtPath:localizationPath | ||
withIntermediateDirectories:false | ||
attributes:nil | ||
error:&error]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just flatten these all into one path, but set withIntermediateDirectories:true
.
|
||
protected: | ||
virtual void SetUp() { | ||
NSString* _subDirectory = @"en.lproj"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?? you're only testing looking strings up in the single lproj that exists, which usually matches the system locale.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point was to always find a localization we have. Since we know we can use English as a fallback the test doesn't care about what locale or language settings the machine has. This way we can ensure we're testing the localization changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
by testing the base localization, we aren't testing anything related to 1. localization or 2. localization changes though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, we're certainly testing the NSLocalizedString path. Grabbing a particular locale is not the goal here, nor is that something I think we can rely on with a unit test. The system can only have one locale setting, so it doesn't seem to matter to me which locale that is. Since English is a known fallback is that not acceptable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems fine now!
CFStringDelete(truncatedString, CFRangeMake(cutoff.location, CFStringGetLength(truncatedString) - cutoff.location)); | ||
return truncatedString; | ||
return CFStringCreateWithSubstring(NULL, localization, CFRangeMake(0, cutoff.location)); | ||
//CFMutableStringRef truncatedString = CFStringCreateMutableCopy(NULL, 0, localization); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
commented!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh woops. I wanted to keep that around in case I goofed anything in the substring call.
@@ -28,10 +28,10 @@ @implementation LocalizationViewController { | |||
UILabel* _pasteTextLabel; | |||
} | |||
- (void)viewDidLoad { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: you may need [super viewDidLoad]
here for completeness' sake
@@ -30,20 +30,13 @@ virtual void SetUp() { | |||
NSString* _subDirectory = @"en.lproj"; | |||
NSString* localizationResourceName = @"Localizable.strings"; | |||
// Create a unique test directory | |||
NSString* tempDir = [[@"./tmp_TestFoundation" stringByAppendingString:[NSUUID UUID].UUIDString] stringByAppendingString:@"/"]; | |||
|
|||
NSString* tempDir = [[@"./tmp_TestFoundation" stringByAppendingPathComponent:[NSUUID UUID].UUIDString] stringByAppendingPathComponent:@"/"]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/
is not a path component, so you can leave this call out.
NSString* tempDir = [[@"./tmp_TestFoundation" stringByAppendingString:[NSUUID UUID].UUIDString] stringByAppendingString:@"/"]; | ||
|
||
NSString* tempDir = [[@"./tmp_TestFoundation" stringByAppendingPathComponent:[NSUUID UUID].UUIDString] stringByAppendingPathComponent:@"/"]; | ||
NSString* bundlePath = [tempDir stringByAppendingPathComponent:_bundleName]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
funny question: why not just expose bundlePath in the fixture and use that instead of re-combining _playground
and _bundleName
later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not funny at all. That seems like a good idea to avoid extra string work. 👍
@@ -61,14 +57,15 @@ virtual void TearDown() { | |||
} | |||
|
|||
StrongId<NSString> _playground; | |||
StrongId<NSString> bundlePath; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_
@@ -25,6 +25,11 @@ | |||
#endif | |||
#include <ctype.h> | |||
|
|||
// WINOBJC: Helper for retrieving list of preferred languages | |||
#if DEPLOYMENT_TARGET_WINDOWS | |||
#include "_CFLocaleInternal.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this still necessary?
you don't need to take all of my feedback; you are free to reject some. |
Thank you for this fix! Just to clarify, does this only support getting localized strings or does it enable getting any kind of localized bundle resource? |
It should have fixed a fundamental problem in lproj lookup. The problem was that an API for looking up localized resources did not have support for retrieving user preferred languages. This has been solved. If you have some other particular APIs you would like us to take a look at, please let us know. This fix was really focused on ensuring localized strings were functional. |
Sounds great, thanks! |
* Fix NSLocalizedString and add new tests * Address CR Feedback. * Forgot to change to StringByAppendingPath * Nevermind. ASSERT_OBJCEQ does indeed work. * Address feedback. * Address feedback.
Add automated tests using NSLocalizedStringFromTableInBundle and a fabricated bundle. Also add a page in WoCCatalog for testing various system languages and NSLocalizedString from the main bundle.
Fixes #2791
This change is