Live Rendering и локализация

Есть в Xcode отличная возможность сразу в интерфейсе Interface Builder видеть итоговый результат отрисовки отличных от стандартных компонентов. Например, вы можете сделать свою кнопку с обводкой, добавив три свойства:

IB_DESIGNABLE
@interface MyButton : UIButton
@property (nonatomic, assign) IBInspectable CGFloat lineWidth;
@property (nonatomic, assign) IBInspectable CGFloat cornerRadius;
@property (nonatomic, copy) IBInspectable UIColor *lineColor;
@end

Значения этим свойствам можно буден назначить в панели инспектора при редактировании xib или storyboard, а при правильной реализации класса эти изменения тут же отобразятся на вашей кнопке. Подробнее прочитать можно тут, тут и в официальной документации.

Я же хочу поделиться результатом компиляции нескольких идей относительно локализации объектов, находящихся в xib или storyboard.

В комментария к статье iOS Localization: XLIFF была предложена идея использовать IBInspectable свойство для задания ключа локализации, который в коде используется для доступа к строкам из файлов Localizable.strings:

@property (nonatomic, copy) IBInspectable NSString *locKey;

а в реализации метода-сеттера предполагалось просто установить строку:

-(void)setLocKey:(NSString *)locKey
{
    _locKey = [locKey copy];
    NSString *locString = NSLocalizedString(locKey, nil);
    [self setTitle:locString forState:UIControlStateNormal];
}

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

@interface UIView (Localizables)
@property (nonatomic, copy) IBInspectable NSString *locKey;
@end

@implementation UIView (Localizables)

-(void)setLocKey:(NSString *)locKey
{
    if (locKey && locKey.length > 0) {
        NSString *locString = MLLocalizedString(locKey, nil);
        // Button
        if ([self isKindOfClass:[UIButton class]]) {
            [((UIButton*)self) setTitle:locString forState:UIControlStateNormal];
        }
        // Label
        else if ([self isKindOfClass:[UILabel class]]) {
            [((UILabel*)self) setText:locString];
        }
        // Text View
        else if ([self isKindOfClass:[UITextView class]]) {
            [((UITextView*)self) setText:locString];
        }
        // Text Field
        else if ([self isKindOfClass:[UITextField class]]) {
            [((UITextField*)self) setText:locString];
        }
        // Image View
        else if ([self isKindOfClass:[UIImageView class]]) {
            [((UIImageView*)self) setImage:[UIImage imageNamed:locString
                                                      inBundle:[NSBundle bundleForClass:[AppDelegate class]]
                                 compatibleWithTraitCollection:nil]];
        }
    }
}

-(NSString *)locKey
{
    return nil;
}

@end

Стоит отметить, что если использовать стандартный макрос NSLocalizedString(), то в механизме отрисовки будет происходить небольшой сбой, описанный в данной статье, а конкретно — вместо строки из Localizable.strings будет отображаться ключ locKey, так как +mainBundle в макросе будет возвращать не бандл вашего приложения, а другой.

Чтобы исправить это недоразумение, сделаем свой макрос на основе оригинального и чуть-чуть его подредактируем:

// Localization for IB support
#define MLLocalizedString(key, comment) \
[[NSBundle bundleForClass:[AppDelegate class]] localizedStringForKey:(key) value:@"" table:nil]
#define MLLocalizedStringFromTable(key, tbl, comment) \
[[NSBundle bundleForClass:[AppDelegate class]] localizedStringForKey:(key) value:@"" table:(tbl)]

Здесь AppDelegate — это класс-наследник от UIResponder, который используется в нашем приложении, тем самым возвращаемый NSBundle гарантированно будет относиться к нашему приложению и будет содержать строки Localizable.strings.

К сожалению, описанный ниже приём сработал лишь один раз и более не воспроизводил описанный результат. В чем причина, я пока не разобрался.

Если вам требуется, чтобы Interface Builder выбирал для отображения конкретный язык, установите в настройках текущей выбранной схемы аргументы запуска:

  • выбрать схему
  • выбрать Edit Scheme в выпадающем меню
  • выбрать Run
  • выбрать Arguments
  • добавить -AppleLocale de_De
  • выбрать -AppleLanguages (de)

Если у вас несколько схем, то после переключения достаточно выбрать в меню Editor пункт Refresh All Views и после успешной пересборки в  Interface Builder отображаемые строки заменятся на соответствующие выбранной схеме.

Единственный недостаток – это время, которое занимает пересборка: если ваш Mac не самый быстрый, придётся терпеливо выжидать, пока пройдёт компиляция и обработка ресурсов. Так же раздражает, что IB производит компиляцию и сборку для всех таргетов проекта или рабочего пространства, не обращая внимания на то, какая схема и таргет в ней выбраны.


Опубликовано

в

от

Комментарии

Добавить комментарий