Cómo forzar a NSLocalizedString a usar un idioma específico
En iPhone NSLocalizedString
devuelve la cadena en el idioma del iPhone. ¿Es posible forzar NSLocalizedString
el uso de un idioma específico para tener la aplicación en un idioma diferente al del dispositivo?
NSLocalizedString()
(y sus variantes) acceda a la clave "AppleLanguages" para NSUserDefaults
determinar cuáles son las configuraciones del usuario para los idiomas preferidos. Esto devuelve una serie de códigos de idioma, siendo el primero el establecido por el usuario para su teléfono y los siguientes utilizados como alternativas si un recurso no está disponible en el idioma preferido. (en el escritorio, el usuario puede especificar varios idiomas con un orden personalizado en Preferencias del Sistema)
Puede anular la configuración global para su propia aplicación si lo desea utilizando el método setObject:forKey: para configurar su propia lista de idiomas. Esto tendrá prioridad sobre el valor establecido globalmente y se devolverá a cualquier código de su aplicación que esté realizando la localización. El código para esto sería algo como:
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:@"de", @"en", @"fr", nil] forKey:@"AppleLanguages"];
[[NSUserDefaults standardUserDefaults] synchronize]; //to make the change immediate
Esto haría que el alemán sea el idioma preferido para su solicitud, con el inglés y el francés como opciones alternativas. Le recomendamos llamar a esto en algún momento temprano en el inicio de su aplicación. Puede leer más sobre las preferencias de idioma/localización aquí: Temas de programación de internacionalización: obtención del idioma y la configuración regional actuales
Tuve el mismo problema recientemente y no quería iniciar ni parchear todo mi NSLocalizedString ni forzar el reinicio de la aplicación para que funcionara el nuevo idioma. Quería que todo funcionara como está.
Mi solución fue cambiar dinámicamente la clase del paquete principal y cargar allí el paquete apropiado:
Archivo de cabecera
@interface NSBundle (Language)
+(void)setLanguage:(NSString*)language;
@end
Implementación
#import <objc/runtime.h>
static const char _bundle=0;
@interface BundleEx : NSBundle
@end
@implementation BundleEx
-(NSString*)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName
{
NSBundle* bundle=objc_getAssociatedObject(self, &_bundle);
return bundle ? [bundle localizedStringForKey:key value:value table:tableName] : [super localizedStringForKey:key value:value table:tableName];
}
@end
@implementation NSBundle (Language)
+(void)setLanguage:(NSString*)language
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
object_setClass([NSBundle mainBundle],[BundleEx class]);
});
objc_setAssociatedObject([NSBundle mainBundle], &_bundle, language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]] : nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
Básicamente, cuando se inicia tu aplicación y antes de cargar tu primer controlador, simplemente llama:
[NSBundle setLanguage:@"en"];
Cuando su usuario cambie su idioma preferido en su pantalla de configuración, simplemente llámelo nuevamente:
[NSBundle setLanguage:@"fr"];
Para restablecer los valores predeterminados del sistema, simplemente pase nil:
[NSBundle setLanguage:nil];
Disfrutar...
Para aquellos que necesitan una versión Swift:
var bundleKey: UInt8 = 0
class AnyLanguageBundle: Bundle {
override func localizedString(forKey key: String,
value: String?,
table tableName: String?) -> String {
guard let path = objc_getAssociatedObject(self, &bundleKey) as? String,
let bundle = Bundle(path: path) else {
return super.localizedString(forKey: key, value: value, table: tableName)
}
return bundle.localizedString(forKey: key, value: value, table: tableName)
}
}
extension Bundle {
class func setLanguage(_ language: String) {
defer {
object_setClass(Bundle.main, AnyLanguageBundle.self)
}
objc_setAssociatedObject(Bundle.main, &bundleKey, Bundle.main.path(forResource: language, ofType: "lproj"), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
Normalmente hago esto de esta manera, pero DEBES tener todos los archivos de localización en tu proyecto.
@implementation Language
static NSBundle *bundle = nil;
+(void)initialize
{
NSUserDefaults* defs = [NSUserDefaults standardUserDefaults];
NSArray* languages = [defs objectForKey:@"AppleLanguages"];
NSString *current = [[languages objectAtIndex:0] retain];
[self setLanguage:current];
}
/*
example calls:
[Language setLanguage:@"it"];
[Language setLanguage:@"de"];
*/
+(void)setLanguage:(NSString *)l
{
NSLog(@"preferredLang: %@", l);
NSString *path = [[ NSBundle mainBundle ] pathForResource:l ofType:@"lproj" ];
bundle = [[NSBundle bundleWithPath:path] retain];
}
+(NSString *)get:(NSString *)key alter:(NSString *)alternate
{
return [bundle localizedStringForKey:key value:alternate table:nil];
}
@end
No lo use en iOS 9. Esto devuelve cero para todas las cadenas que pasan a través de él.
Encontré otra solución que le permite actualizar las cadenas de idioma, sin reiniciar la aplicación y compatible con genstrings:
Coloque esta macro en Prefix.pch:
#define currentLanguageBundle [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:[[NSLocale preferredLanguages] objectAtIndex:0] ofType:@"lproj"]]
y donde sea que necesites una cadena localizada, usa:
NSLocalizedStringFromTableInBundle(@"GalleryTitleKey", nil, currentLanguageBundle, @"")
Para configurar el uso del idioma:
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObject:@"de"] forKey:@"AppleLanguages"];
Funciona incluso con saltos de idioma consecutivos como:
NSLog(@"test %@", NSLocalizedStringFromTableInBundle(@"NewKey", nil, currentLanguageBundle, @""));
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObject:@"fr"] forKey:@"AppleLanguages"];
NSLog(@"test %@", NSLocalizedStringFromTableInBundle(@"NewKey", nil, currentLanguageBundle, @""));
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObject:@"it"] forKey:@"AppleLanguages"];
NSLog(@"test %@", NSLocalizedStringFromTableInBundle(@"NewKey", nil, currentLanguageBundle, @""));
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObject:@"de"] forKey:@"AppleLanguages"];
NSLog(@"test %@", NSLocalizedStringFromTableInBundle(@"NewKey", nil, currentLanguageBundle, @""));
Como dije antes, simplemente haz:
[[NSUserDefaults standardUserDefaults] setObject: [NSArray arrayWithObjects:@"el", nil] forKey:@"AppleLanguages"];
Pero para evitar tener que reiniciar la aplicación, coloque la línea en el método principal de main.m
, justo antes de UIApplicationMain
(...).