Noveo

Наш блог Обмен премудростями: cмена локализации iOS приложения во время выполнения

Обмен премудростями: cмена локализации iOS приложения во время выполнения

image

Новеовчане – народ нежадный, и когда кому-то из нас есть, чем поделиться в плане накопленных знаний и/или опыта, во внутреннем блоге появляется пост неоспоримой практической ценности :). Посмотрев на список уже опубликованных постов, мы подумали: а почему бы, собственно, не поделиться еще и с общественностью? И решили периодически публиковать некоторые интересные посты из внутреннего блога во внешний.  Встречайте дебютный пост в этом разделе :). Автор – наш iOS-разработчик Александр.

Смена локализации iOS приложения во время выполнения

Многие приложения требуют поддержки многоязычности. Даже если (пока) не требуют, лучше (сразу) хранить все строки в одном месте: так удобнее вносить правки и всегда будет легко добавить многоязычность. Apple предлагает использовать файл Localizable.strings для хранения локализованных строк в формате "ключ" = "значение" и получать строки из кода по ключу с помощью функции NSLocalizableString. Для большинства приложений это работает, но есть исключения…

Что не так с функцией NSLocalizableString?

  • Она не предназначена для смены языка во время выполнения приложения.
  • Она не позволяет пользователю выбрать язык в отдельном приложении, только в масштабах системы.
  • Она поддерживает только языки, поддерживаемые операционной системой.

Решение, которое мы применили в проекте, решает все эти проблемы и имеет следующее поведение:

  • При первом запуске приложения считывается язык из настроек девайса и используется для локализации (если для него есть перевод).
  • Если для языка девайса нет перевода, берётся язык по умолчанию.
  • Пользователь может поменять язык внутри приложения, при этом все вьюхи автоматически перерисовывают что надо.

Используются библиотеки AMLocalizableStrings (для получения строк с поддержкой смены языка во время выполнения) и ReactiveCocoa (для удобного слежения за сменой языка и перерисовки интерфейса). Сами строки хранятся в тот же самом файле Localizable.strings, который использовался для стандартного NSLocalizableString().

Ниже приведён достаточно полный код, возможно даже избыточный, зато готовый к использованию.

/*
    В файле перечислены поддерживаемые языки и объявлены функции для получения атрибутов языка
    (человеко–читаемое название, иконку, параметры API для выбора языка и т.д.).
    Как вариант развития — язык становится полноценным объектом и содержит все эти атрибуты.
*/
 
LocalizationUtils.h
-------------------
 
typedef NS_ENUM(NSInteger, PGLanguage) {
    kPGLanguageUnknown = -1,
    kPGLanguageDE = 0,
    kPGLanguageFR,
    kPGLanguageEN
};
 
extern const PGLanguage kDefaultLanguage;
 
NSArray *getLanguagesList (void);
NSString *codeForLanguage (PGLanguage language);
 
LocalizationUtils.m
-------------------
 
const PGLanguage kDefaultLanguage = kPGLanguageEN;
 
NSArray *getLanguagesList (void)
{
    return @[
        @(kPGLanguageDE),
        @(kPGLanguageFR),
        @(kPGLanguageEN)
    ]
}
 
NSString *codeForLanguage(PGLanguage language)
{
    switch (language) {
        case kPGLanguageDE: return @"de";
        case kPGLanguageFR: return @"fr";
        case kPGLanguageEN: return @"en";
        default: return nil;
    }
}

/*
    Объект класса UserProfile представляет сущность Пользователь и содержит данные о нём:
    язык интерфеса, логин, пароль, список избранного и т.д.
    В примере данные хранятся в UserDefaults.
*/
 
UserProfile.h
-------------
 
@interface UserProfile : NSObject
@property (assign, nonatomic) PGLanguage language;
@end
 
UserProfile.m
-------------
#import <ReactiveCocoa.h>
#import "LocalizationSystem.h"
#import "LocalizationUtils.h"
 
@implementation UserProfile
 
- (instancetype)init
{
    self = [super init];
    if (self != nil) {
        [self loadSettings];
    }
    return self;
}
 
- (void)setLanguage:(PGLanguage)language
{
    _language = language;
    NSString *languageCode = codeForLanguage(language);
    [[LocalizationSystem sharedLocalSystem] setLanguage:(languageCode)];
 
    // Store settings to disk
    [[NSUserDefaults standardUserDefaults] setObject:@(self.language) forKey:kUDKeyUserLanguage];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
 
- (void)loadSettings
{
    NSNumber *userLanguage = [[NSUserDefaults standardUserDefaults] objectForKey:kUDKeyUserLanguage];
 
    // Select device language or default language for the first launch
    if (userLanguage == nil) {
       self.language = kDefaultLanguage;
        NSString *deviceLanguage = [[NSLocale preferredLanguages] objectAtIndex:0];
        for (NSNumber *language in getLanguagesList()) {
            if ([deviceLanguage isEqualToString:codeForLanguage(language.integerValue)]) {
                self.language = language.integerValue;
                break;
            }
        }
    }
 
    // Load previously selected language
    else {
        self.language = userLanguage.integerValue;
    }    
}
 
@end
/*
    Этот класс — пример вью–контроллера, содержащего локализованные ресурсы.
    Дополнительно этот вью–контроллер умеет менять язык пользователя (обычно экран настроек).
*/
 
SomeVC.m
--------
 
#import <ReactiveCocoa.h>
#import "LocalizationSystem.h"
#import "UserProfile.h"
 
@interface SomeVC ()
@property (strong) UserProfile *profile;
@end
 
@implementation SomeVC
 
- (void)viewDidLoad
{
    [super viewDidLoad];
 
    // Update localization
    [RACAbleWithStart(self, profile.language) subscribeNext:^(id x) {
        self.title = AMLocalizedString(@"SomeVC.Title", @"");
    }];
}
 
// Call this method from UI code when user selects language
- (void)selectLanguage:(PGLanguage)language
{
    self.profile.language = language;
}
 
@end

Искренне рады, если смогли кому-то помочь этим постом. Оставайтесь с нами, вас ждет еще много интересного! :)

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

НазадПредыдущий пост ВпередСледующий пост

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: