Изменение текущей локали симулятора iOS во время выполнения

При разработке набора вычислений дат и языковых правил для преобразования числовых значений и дат в строки я пишу тесты, которые утверждают результат метода форматирования строк. Мнимое утверждение для него может выглядеть так:

NSAssert([dateString isEqualToString:@"Three days, until 6:00 PM"], @"Date string should match expectation");

Однако, поскольку приложение локализовано для нескольких языков, а мои коллеги-разработчики также находятся в разных локалях, а не в разных местах, может случиться, что ваше устройство или симулятор настроено на другой язык, чем тот, который тестирует написан для. В подобном сценарии содержимое dateString может быть примерно таким:

@"Drie dagen, tot 18:00" // the assertion fails
@"Drei Tage, bis 18 Uhr" // the assertion also fails

Это может быть или не быть правильной датой даты для этих локалей, но часть, о которой идет речь, - это способ запуска тестов в определенную локаль, когда базовый код использует API Apple, как это

[NSDateFormatter localizedStringFromDate:date 
                 dateStyle:NSDateFormatterNoStyle 
                 timeStyle:NSDateFormatterShortStyle];

Я хотел бы прикрыть свои слова на двух или более языках, например:

[NSSomething actionToSetTheLocaleTo:@"en_US"];
dateString = ...; // the formatting
NSAssert([dateString isEqualToString:@"Three days, until 6:00 PM"], @"match en_US");

[NSSomething actionToSetTheLocaleTo:@"nl_NL"];
dateString = ...; // the formatting
NSAssert([dateString isEqualToString:@"Drie dagen, tot 18:00"], @"match nl_NL");

Кто знает, как добиться этого эффекта?

Примечания:

  • Изменение предпочтительного языка не сокращает его, оно также должно влиять на поведение NSDateFormatter и NSNumberFormatter.
  • Поскольку это только для тестирования модулей, я бы хотел использовать частный API. Однако, в интересах других людей, спотыкающихся об этом посту, предпочтительным является публичный API.
  • Передача пользовательской локали для каждого API-интерфейса даты или числа может быть окончательным, но я отправляю этот вопрос, надеясь, что избежать вернется к этим экстремальным мерам. Если вы, однако, знаете, что это единственное решение, предоставьте некоторую ссылку, и я больше не буду тратить время.

Ссылки по теме:

Ответ 1

@Desmond указал на рабочее решение. Пока он не поставит здесь ответ, чтобы разместить эту информацию, позвольте мне подвести итог тому, что я закончил с небольшим количеством кода.

Решение, оказывается, "легко", как swizzling the methods, которое методы класса используют внутри:

beforeEach(^{
    [NSBundle ttt_overrideLanguage:@"nl"];
    [NSLocale ttt_overrideRuntimeLocale:[NSLocale localeWithLocaleIdentifier:@"nl_NL"]];
});

afterEach(^{
    [NSLocale ttt_resetRuntimeLocale];
    [NSBundle ttt_resetLanguage];
});

Методы ttt_..., которые вы видите выше, используют категории NSObject, NSLocale и NSBundle для проверки во время выполнения, должны ли они использовать исходные методы или возвращать что-то еще. Этот метод работает безупречно, когда вы пишете свои тесты, и хотя он технически не использует какой-либо частный API, я бы настоятельно рекомендовал использовать его только в тестовой настройке, а не за все, что вы отправляете в App Store для просмотра.

В этом разделе вы найдете категории Objective-C, которые я добавил в приложение для тестирования, чтобы достичь требуемого поведения.

Ответ 2

Так как это в unit test, вы пробовали установить локаль на NSDateFormatter? Вам не нужно устанавливать языковой стандарт на весь симулятор, вы можете сделать его параметром своих тестов. Большинство методов cocoa, зависящих от языка, могут принимать его как параметр или свойство.

Что-то вроде:

NSLocale *locale = [NSLocale localeWithLocaleIdentifier:@"en-US"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setLocale:locale];
[formatter setDateStyle: NSDateFormatterNoStyle];
[formatter setTimeStyle: NSDateFormatterShortStyle];
[formatter setFormatterBehavior:NSDateFormatterBehavior10_4];
thing = [formatter stringForObjectValue:date];

ЛокализованныйStringFromDate: dateStyle: метод timeStyle: вы используете описанный здесь, и он довольно подробно описывает все, кроме локали. Таким образом, вы можете сделать те же шаги, но установите локаль в нечто иное, чем текущая локаль системы, используя шаги, описанные выше.

Ответ 3

Единственный способ, который я вижу, - это то, что упомянуто quellish. Однако вы упомянули, что он существует во многих местах.

Вместо того, чтобы переписывать весь ваш текущий код, в вашем pch вы могли бы сделать что-то вроде

#import "UnitTestDateFormatter.h"
#define NSDateFormatter UnitTestDateFormatter

И затем просто создайте подкласс для его обработки:

@implementation UnitTestDateFormatter

- (id) init
{
    self = [super init];

    if(self != nil)
    {
        [self setLocale:...];
    }

    return self;
}

@end

По крайней мере, ваш код может оставаться без изменений.