ручной выбор языка в iOS-приложении (iPhone и iPad) - PullRequest
68 голосов
/ 30 марта 2012

Мой вопрос:

Как мое iPhone-приложение может сообщить iOS, что пользователь выбрал язык в настройках приложения, который отличается от языка, установленного в общих настройках?

Другая формулировка того же вопроса:

Как я могу сказать системе, что NSLocalizedString (@"text", @"comment"); должен иметь доступ не к общесистемному выбранному языку, а к языку, выбранному в приложении?

фон, пример:

Пожалуйста, возьмите эту ситуацию в качестве примера: Сын немецких иммигрантов живет на северо-востоке Франции, рядом с Люксембургом и Германией. Его родным языком является французский, поэтому он установил язык интерфейса пользователя своего iPhone на французский (Настройки -> Общие -> Международные -> Язык -> Французский). Но благодаря своему культурному прошлому и тому, что регион, в котором он живет, является двуязычным, он также очень хорошо говорит по-немецки. Но он не говорит десять слов по-английски. На iPhone (и на iPad тоже) у него нет шансов выбрать второй язык, поэтому телефон знает только, что он говорит по-французски. Он не знает навыков пользователей на других языках.

Теперь приходит мое приложение: я разработал его на английском и немецком языках (немецкий - мой родной язык, а английский - стандартный язык в IT). Я разработал его в соответствии со всеми правилами и рекомендациями для многоязычных iOS-приложений. «Первый» язык (язык по умолчанию) моего приложения - английский.

Это означает:

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

Но если он выбрал какой-либо другой язык (например, китайский, польский или французский) в общих настройках, он получит язык приложения по умолчанию, который, в моем случае, английский. Но для моего французско-немецкого друга это не лучший выбор. Он хотел бы использовать существующую немецкую версию, но, похоже, нет возможности позволить пользователю выбрать эту версию.

Добавление французского перевода решило бы проблему для нашего французско-немецкого друга, но не для людей, говорящих на двух других языках (таких как итальянский и немецкий), и я не могу поддерживать мое приложение со всеми языками, на которых говорят на этой планете. Установка языка по умолчанию на немецкий также не оптимальна, потому что это поднимает ту же проблему для людей, говорящих на французском (родной язык) и английском (как на втором языке).

Так что я думаю, что мое приложение должно иметь возможность вручную выбирать язык, который отличается от предварительно выбранного языка. Добавление выбора языка в панель настроек приложений - не проблема. Но как я могу сказать системе, что NSLocalizedString (@"text", @"comment"); должен иметь доступ не к общесистемному выбранному языку, а к языку, выбранному в приложении?

Ответы [ 6 ]

83 голосов
/ 13 апреля 2012

Тем временем я нашел решение своей проблемы для себя:

Я создал новый класс "LocalizeHelper":


Заголовок LocalizeHelper.h

//LocalizeHelper.h

#import <Foundation/Foundation.h>

// some macros (optional, but makes life easy)

// Use "LocalizedString(key)" the same way you would use "NSLocalizedString(key,comment)"
#define LocalizedString(key) [[LocalizeHelper sharedLocalSystem] localizedStringForKey:(key)]

// "language" can be (for american english): "en", "en-US", "english". Analogous for other languages.
#define LocalizationSetLanguage(language) [[LocalizeHelper sharedLocalSystem] setLanguage:(language)]

@interface LocalizeHelper : NSObject

// a singleton:
+ (LocalizeHelper*) sharedLocalSystem;

// this gets the string localized:
- (NSString*) localizedStringForKey:(NSString*) key;

//set a new language:
- (void) setLanguage:(NSString*) lang;              

@end

iMplementation LocalizeHelper.m

// LocalizeHelper.m
#import "LocalizeHelper.h"

// Singleton
static LocalizeHelper* SingleLocalSystem = nil;

// my Bundle (not the main bundle!)
static NSBundle* myBundle = nil;


@implementation LocalizeHelper


//-------------------------------------------------------------
// allways return the same singleton
//-------------------------------------------------------------
+ (LocalizeHelper*) sharedLocalSystem {
    // lazy instantiation
    if (SingleLocalSystem == nil) {
        SingleLocalSystem = [[LocalizeHelper alloc] init];
    }
    return SingleLocalSystem;
}


//-------------------------------------------------------------
// initiating
//-------------------------------------------------------------
- (id) init {
    self = [super init];
    if (self) {
        // use systems main bundle as default bundle
        myBundle = [NSBundle mainBundle];
    }
    return self;
}


//-------------------------------------------------------------
// translate a string
//-------------------------------------------------------------
// you can use this macro:
// LocalizedString(@"Text");
- (NSString*) localizedStringForKey:(NSString*) key {
    // this is almost exactly what is done when calling the macro NSLocalizedString(@"Text",@"comment")
    // the difference is: here we do not use the systems main bundle, but a bundle
    // we selected manually before (see "setLanguage")
    return [myBundle localizedStringForKey:key value:@"" table:nil];
}


//-------------------------------------------------------------
// set a new language
//-------------------------------------------------------------
// you can use this macro:
// LocalizationSetLanguage(@"German") or LocalizationSetLanguage(@"de");
- (void) setLanguage:(NSString*) lang {

    // path to this languages bundle
    NSString *path = [[NSBundle mainBundle] pathForResource:lang ofType:@"lproj" ];
    if (path == nil) {
        // there is no bundle for that language
        // use main bundle instead
        myBundle = [NSBundle mainBundle];
    } else {

        // use this bundle as my bundle from now on:
        myBundle = [NSBundle bundleWithPath:path];

        // to be absolutely shure (this is probably unnecessary):
        if (myBundle == nil) {
            myBundle = [NSBundle mainBundle];
        }
    }
}


@end

Для каждого языка, который вы хотите поддерживать, вам нужен файл с именем Localizable.strings,Это работает точно так, как описано в документации Apple для локализации.Единственное отличие: теперь вы даже можете использовать такие языки, как хинди или эсперанто, которые не поддерживаются Apple.

Для примера приведем первые строки моих английских и немецких версий Localizable.strings:

Английский

/* English - English */

/* for debugging */
"languageOfBundle" = "English - English";

/* Header-Title of the Table displaying all lists and projects */
"summary" = "Summary";

/* Section-Titles in table "summary" */
"help" = "Help";
"lists" = "Lists";
"projects" = "Projects";
"listTemplates" = "List Templates";
"projectTemplates" = "Project Templates";

Немецкий

/* German - Deutsch */

/* for debugging */
"languageOfBundle" = "German - Deutsch";

/* Header-Title of the Table displaying all lists and projects */
"summary" = "Überblick";

/* Section-Titles in table "summary" */
"help" = "Hilfe";
"lists" = "Listen";
"projects" = "Projekte";
"listTemplates" = "Vorlagen für Listen";
"projectTemplates" = "Vorlagen für Projekte";

Для использования локализации необходимо иметь некоторые настройки-программы в вашем приложении и при выборе языка вы вызываете макрос:

LocalizationSetLanguage(selectedLanguage);

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

Чтобы локализованные тексты были доступны для каждой ситуации, вы НИКОГДА не должны писать фиксированные тексты в заголовки объектов.ВСЕГДА используйте макрос LocalizedString(keyword).

не:

cell.textLabel.text = @"nice title";

do:

cell.textLabel.text = LocalizedString(@"nice title");

и иметь запись «хороший заголовок» в каждой версии Localizable.strings!

39 голосов
/ 30 марта 2012

Просто добавьте следующее на экран с выбором языка:

    NSString *tempValue = //user chosen language. Can be picker view/button/segmented control/whatever. Just get the text out of it
    NSString *currentLanguage = @"";
    if ([tempValue rangeOfString:NSLocalizedString(@"English", nil)].location != NSNotFound) {
        currentLanguage = @"en";
    } else if ([tempValue rangeOfString:NSLocalizedString(@"German", nil)].location != NSNotFound) {
        currentLanguage = @"de";
    } else if ([tempValue rangeOfString:NSLocalizedString(@"Russian", nil)].location != NSNotFound) {
        currentLanguage = @"ru";
    }
    [[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:currentLanguage, nil] forKey:@"AppleLanguages"];
    [[NSUserDefaults standardUserDefaults]synchronize];

Затем попросите их перезапустить приложение, и приложение будет на другом языке.

Надеюсь, это поможет

18 голосов
/ 21 января 2017

Вот готовое к использованию и пошаговое руководство о том, как использовать подход Новарга в Swift 3 :


Шаг № 1: Реализация выбора языка

Как лучше всего это сделать, зависит от вас и зависит от проекта. Но используйте

Bundle.main.localizations.filter({ $0 != "Base" }) // => ["en", "de", "tr"]

для программного получения списка всех поддерживаемых вами языков . Также вы можете использовать

Locale.current.localizedString(forLanguageCode: "en") // replace "en" with your variable

для представления имени языка в приложениях текущего языка .

В качестве законченного примера вы можете представить лист действий всплывающего окна после нажатия кнопки, подобной этой:

@IBOutlet var changeLanguageButton: UIButton!

@IBAction func didPressChangeLanguageButton() {
    let message = "Change language of this app including its content."
    let sheetCtrl = UIAlertController(title: "Choose language", message: message, preferredStyle: .actionSheet)

    for languageCode in Bundle.main.localizations.filter({ $0 != "Base" }) {
        let langName = Locale.current.localizedString(forLanguageCode: languageCode)
        let action = UIAlertAction(title: langName, style: .default) { _ in
            self.changeToLanguage(languageCode) // see step #2
        }
        sheetCtrl.addAction(action)
    }

    let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
    sheetCtrl.addAction(cancelAction)

    sheetCtrl.popoverPresentationController?.sourceView = self.view
    sheetCtrl.popoverPresentationController?.sourceRect = self.changeLanguageButton.frame
    present(sheetCtrl, animated: true, completion: nil)
}

Шаг № 2: Объяснить пользователю, что делать + Изменить язык при перезагрузке

Возможно, вы заметили, что код на шаге # 1 вызывает метод с именем changeToLanguage(langCode:). Это , что вы должны делать, также , когда пользователь выбирает новый язык для изменения, независимо от того, как вы его создали. Вот его реализация , просто скопируйте его в свой проект:

private func changeToLanguage(_ langCode: String) {
    if Bundle.main.preferredLocalizations.first != langCode {
        let message = "In order to change the language, the App must be closed and reopened by you."
        let confirmAlertCtrl = UIAlertController(title: "App restart required", message: message, preferredStyle: .alert)

        let confirmAction = UIAlertAction(title: "Close now", style: .destructive) { _ in
            UserDefaults.standard.set([langCode], forKey: "AppleLanguages")
            UserDefaults.standard.synchronize()
            exit(EXIT_SUCCESS)
        }
        confirmAlertCtrl.addAction(confirmAction)

        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
        confirmAlertCtrl.addAction(cancelAction)

        present(confirmAlertCtrl, animated: true, completion: nil)
    }
}

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

UserDefaults.standard.set([langCode], forKey: "AppleLanguages")
UserDefaults.standard.synchronize() // required on real device

Шаг № 3 (необязательно): локализация строк

Возможно, вы захотите локализовать строки, такие как «Закрыть сейчас», используя макрос NSLocalizedString (или любой другой улучшенный метод).


Пример из реального мира

Я использую эту точную реализацию в приложении, ориентированном на для iOS 10, я могу подтвердить, что он работает для меня как на симуляторе, так и на устройстве. Приложение на самом деле с открытым исходным кодом , так что вы можете найти приведенный выше код, распределенный по различным классам здесь .

3 голосов
/ 29 августа 2017

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

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

В настройках iOS вы можете установить дополнительные языки:

iOS language selection preferences

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

Затем, когда ваше приложение будет переведено на английский и немецкий языки, iPhone этого молодого человека выберет немецкие ресурсы.

Это решит проблему?

2 голосов
/ 24 июля 2017

Localize-Swift - Swift-дружественная локализация и i18n с переключением языка в приложении

0 голосов
/ 24 марта 2017

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

UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"App restart required", @"App restart required") message:NSLocalizedString(@"In order to change the language, the App must be closed and reopened by you.", @"In order to change the language, the App must be closed and reopened by you.") preferredStyle:UIAlertControllerStyleActionSheet];

    [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {


        [self dismissViewControllerAnimated:YES completion:^{


        }];
    }]];

    [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Restart", @"Restart") style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {

        [[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:@"ar", nil] forKey:@"AppleLanguages"];
        [[NSUserDefaults standardUserDefaults]synchronize];

        exit(EXIT_SUCCESS);


    }]];


    [self presentViewController:actionSheet animated:YES completion:nil];
}
...