setlocale() doesn't work in iOS simulator?

2019-07-30 08:57发布

问题:

Update: Strangely, setlocale() only fails on the iOS Simulator so I have amended the question title. It works fine on actual devices.

I'm working with native (C/C++) code under iOS 6 and I need to format arbitrary wchar_t strings. However, when formatting strings containing codepoints outside the Latin-1 codepage, swprintf fails (return value -1 with errno=EILSEQ).

wchar_t buff[256];
swprintf(buff, 256, L"\u00A9 %ls", L"ascii"); // works
swprintf(buff, 256, L"\u03A0 %ls", L"ascii"); // will return -1

After asking a related question here, the problem appears to be that the locale is not set correctly (I have verified that the solution works under Mac OS X). But it seems to have no effect under iOS 6:

#include <locale.h>

setlocale(LC_CTYPE,"");

Following the instructions here , I have copied/added the locale files manually to my project, and set the PATH_LOCALE environment variable, but the problem persists:

NSString* resourcePath=[[NSBundle mainBundle] resourcePath];
setenv("PATH_LOCALE", [resourcePath UTF8String], 1);
setlocale(LC_CTYPE,"en_US.UTF-8");

Does anyone know how I can get setlocale() to work under iOS 6 (while still having the app accepted by the Apple Store)?

回答1:

PATH_LOCALE is the base path for the locale files, each locale is in a sub directory like:

fr/LC_TIME fr/.. en/LC...

So you need sub directories, and iOS bundles do not support sub directories.

When you do

setlocale(LC_CTYPE,"en_US.UTF-8");

The C library looks for a directory called "en_US.UTF-8" in PATH_LOCALE.

The solution I found is to add the locales in the bundle as a zip (with the whole sub directory structure), unzip it at first launch in the documents path and set PATH_LOCALE to that new directory.

NSString* documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];

    NSString* localeDir = [documentsPath stringByAppendingPathComponent:@"/locale"];
    BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:localeDir];

    if (!fileExists) {
        NSString *filepath = [[NSBundle mainBundle] pathForResource:@"locale" ofType:@"zip"];
        ZipArchive *zipArchive = [[ZipArchive alloc] init];
        [zipArchive UnzipOpenFile:filepath];
        [zipArchive UnzipFileTo:documentsPath overWrite:YES];
        [zipArchive UnzipCloseFile];
    }
    setenv("PATH_LOCALE", [[NSString stringWithFormat:@"%@/locale", documentsPath] cString], 1);

This uses Ziparchive that you have to include in your project.

https://code.google.com/archive/p/ziparchive/



回答2:

I couldn't get stephane's solution to work. But both of these super-simple solutions worked for me:

  1. setlocale(LC_CTYPE, "UTF-8")
  2. newlocale(LC_CTYPE_MASK, "UTF-8") combined with the foo_l variant wchar_t classes that take a locale argument.

While iOS may not ship with a full suite of locales (including en_US.UTF-8), it appears to ship with a basic language-free UTF-8 one. But it only works with LC_CTYPE; trying LC_ALL will fail. You can of course test with

char *result = setlocale(LC_TYPE, "UTF-8");
Assert(result && strcmp(result, "UTF-8") == 0);