localizable ios objective-c localization internationalization

ios - localizable - Cómo forzar a NSLocalizedString a usar un lenguaje específico



localizable string swift 4 (25)

En pocas palabras:

Localiza tu aplicación

Lo primero que debes hacer es localizar tu aplicación con al menos dos idiomas (inglés y francés en este ejemplo).

Anular NSLocalizedString

En su código, en lugar de usar NSLocalizedString(key, comment) , use una macro MYLocalizedString(key, comment) definida así: #define MYLocalizedString(key, comment) [[MYLocalizationSystem sharedInstance] localizedStringForKey:(key) value:(comment)];

Este singleton de MYLocalizationSystem :

  • Configure langage configurando el derecho NSBundle localizado que solicita el usuario
  • Devuelve la NSString localizada de acuerdo con este idioma previamente establecido

Establecer idioma de usuario

Cuando el usuario cambió el idioma de la aplicación en francés, llame a [[MYLocalizationSystem sharedInstance] setLanguage:@"fr"];

- (void)setLanguage:(NSString *)lang { NSString *path = [[NSBundle mainBundle] pathForResource:lang ofType:@"lproj"]; if (!path) { _bundle = [NSBundle mainBundle]; NSLog(@"Warning: No lproj for %@, system default set instead !", lang); return; } _bundle = [NSBundle bundleWithPath:path]; }

En este ejemplo, este método establece un paquete localizado en fr.lproj

Regresar cadena localizada

Una vez que haya establecido el paquete localizado, podrá obtener la cadena localizada correcta con este método:

- (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)value { // bundle was initialized with [NSBundle mainBundle] as default and modified in setLanguage method return [self.bundle localizedStringForKey:key value:value table:nil]; }

Espero que esto te ayudará.

Encontrará más detalles en este artículo de NSWinery.io

En iPhone, NSLocalizedString devuelve la cadena en el idioma del iPhone. ¿Es posible forzar a NSLocalizedString a usar un idioma específico para tener la aplicación en un idioma diferente al del dispositivo?


¿Qué te parece esta solución para Swift 3?

extension String { func localized(forLanguage language: String = Locale.preferredLanguages.first!.components(separatedBy: "-").first!) -> String { guard let path = Bundle.main.path(forResource: language == "en" ? "Base" : language, ofType: "lproj") else { let basePath = Bundle.main.path(forResource: "Base", ofType: "lproj")! return Bundle(path: basePath)!.localizedString(forKey: self, value: "", table: nil) } return Bundle(path: path)!.localizedString(forKey: self, value: "", table: nil) } }

Uso simple:

"report".localized(forLanguage: "pl") //forced language "report".localized() //default language selected by user in settings, in case when your app doesnt support selected lanaguage, the default one is selected, here is an english.


Como lo menciona Brian Webster, el idioma debe establecerse "en algún momento temprano en el inicio de la aplicación". Pensé que applicationDidFinishLaunching: de AppDelegate debería ser un lugar adecuado para hacerlo, ya que es donde hago todas las demás inicializaciones.

Pero como menciona William Denniss, eso parece tener un efecto solo después de que se reinicia la aplicación, lo cual es un poco inútil.

Sin embargo, parece que funciona bien si pongo el código en la función principal:

int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // Force language to Swedish. [[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObject:@"sv"] forKey:@"AppleLanguages"]; int retVal = UIApplicationMain(argc, argv, nil, nil); [pool release]; return retVal; }

Apreciaría cualquier comentario sobre esto.


Como se dijo anteriormente, solo haz:

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

Pero para evitar tener que reiniciar la aplicación, ponga la línea en el método principal de main.m , justo antes de UIApplicationMain (...).



En el archivo .pch para definir:

#define currentLanguageBundle [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:[[NSLocale preferredLanguages] objectAtIndex:0] ofType:@"lproj"]] #define NSLocalizedString(str,nil) NSLocalizedStringFromTableInBundle(str, nil, currentLanguageBundle, @"")


Generalmente hago esto de esta manera, pero DEBE tener todos los archivos de localización en su 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


Me gusta mejor el método de Mauro Delrio. También he añadido lo siguiente en mi Project_Prefix.pch

#import "Language.h" #define MyLocalizedString(key, alt) [Language get:key alter:alt]

Por lo tanto, si alguna vez desea utilizar el método estándar (que utiliza NSLocalizedString), puede realizar una rápida sustitución de sintaxis en todos los archivos.


Para mi caso tengo dos archivos localizados, ja y en.

y me gustaría forzarlo a en si el idioma preferido en el sistema no es ni ja

Voy a editar el archivo main.m

Verificaré si el primer preferido es en o ja, si no, cambiaré el segundo idioma preferido a en.

int main(int argc, char *argv[]) { [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"AppleLanguages"]; [[NSUserDefaults standardUserDefaults] synchronize]; NSString *lang = [[NSLocale preferredLanguages] objectAtIndex:0]; if (![lang isEqualToString:@"en"] && ![lang isEqualToString:@"ja"]){ NSMutableArray *array = [[NSMutableArray alloc] initWithArray:[NSLocale preferredLanguages]]; [array replaceObjectAtIndex:1 withObject:@"en"]; [[NSUserDefaults standardUserDefaults] setObject:array forKey:@"AppleLanguages"]; [[NSUserDefaults standardUserDefaults] synchronize]; } @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }


Podría crear un subpaquete con el conjunto de cadenas localizadas con las que desea hacer esto y luego usar NSLocalizedStringFromTableInBundle() para cargarlas. (Supongo que este contenido es independiente de la localización normal de la interfaz de usuario que podría estar haciendo en la aplicación).


Puedes hacer algo como esto:

NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"Localizable" ofType:@"strings" inDirectory:nil forLocalization:@"es"]; NSBundle *spanishBundle = [[NSBundle alloc] initWithPath:[bundlePath stringByDeletingLastPathComponent]]; NSLocalizedStringFromTableInBundle(@"House", nil, spanishBundle, nil):


Recientemente tuve el mismo problema y no quería iniciar y parchear todo mi NSLocalizedString ni forzar la aplicación para reiniciarse para que el nuevo idioma funcione. Quería que todo funcionara como está.

Mi solución fue cambiar dinámicamente la clase del paquete principal y cargar el paquete apropiado allí:

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 su aplicación se inicie y antes de cargar su primer controlador, simplemente llame:

[NSBundle setLanguage:@"en"];

Cuando su usuario cambie su idioma preferido en la pantalla de configuración, simplemente llámelo nuevamente:

[NSBundle setLanguage:@"fr"];

Para restablecer a 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) } }


Se me ocurrió una solución que te permite usar NSLocalizedString . Creo una categoría de NSBundle llamada NSBundle+RunTimeLanguage . La interfaz es así.

// NSBundle+RunTimeLanguage.h #import <Foundation/Foundation.h> @interface NSBundle (RunTimeLanguage) #define NSLocalizedString(key, comment) [[NSBundle mainBundle] runTimeLocalizedStringForKey:(key) value:@"" table:nil] - (NSString *)runTimeLocalizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName; @end

La implementación es así.

// NSBundle+RunTimeLanguage.m #import "NSBundle+RunTimeLanguage.h" #import "AppDelegate.h" @implementation NSBundle (RunTimeLanguage) - (NSString *)runTimeLocalizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName { AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate; NSString *path= [[NSBundle mainBundle] pathForResource:[appDelegate languageCode] ofType:@"lproj"]; NSBundle *languageBundle = [NSBundle bundleWithPath:path]; NSString *localizedString=[languageBundle localizedStringForKey:key value:key table:nil]; return localizedString; } @end

Solo agregue importar NSBundle+RunTimeLanguage.h en los archivos que usan NSLocalizedString .

Como puede ver, almaceno mi AppDelegate de idioma en una propiedad de AppDelegate . Esto podría ser almacenado en cualquier lugar que desee.

Esta única cosa que no me gusta de esto es una advertencia que NSLocalizedString marco redefinió. Quizás alguien pueda ayudarme a arreglar esta parte.


Solución Swift 3:

let languages = ["bs", "zh-Hant", "en", "fi", "ko", "lv", "ms", "pl", "pt-BR", "ru", "sr-Latn", "sk", "es", "tr"] UserDefaults.standard.set([languages[0]], forKey: "AppleLanguages")

Dio algunos ejemplos de códigos de idiomas que se pueden utilizar. Espero que esto ayude


Tal vez debería complementar con esto (en el archivo .pch después de #import):

extern NSBundle* bundle; // Declared on Language.m #ifdef NSLocalizedString #undef NSLocalizedString // Delete this line to avoid warning #warning "Undefining NSLocalizedString" #endif #define NSLocalizedString(key, comment) / [bundle localizedStringForKey:(key) value:@"" table:nil]


Versión rápida:

NSUserDefaults.standardUserDefaults().setObject(["fr"], forKey: "AppleLanguages") NSUserDefaults.standardUserDefaults().synchronize()


NSLocalizedString() (y sus variantes) acceden a la clave "AppleLanguages" en NSUserDefaults para 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 posteriores se usan como recursos alternativos si un recurso no está disponible en el idioma preferido. (en el escritorio, el usuario puede especificar varios idiomas con un orden personalizado en las Preferencias del sistema)

Puede anular la configuración global para su propia aplicación si lo desea utilizando el método setObject: forKey: para establecer su propia lista de idiomas. Esto tendrá prioridad sobre el valor establecido globalmente y se devolverá a cualquier código en su aplicación que esté realizando la localización. El código para esto sería algo así como:

[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:@"de", @"en", @"fr", nil] forKey:@"AppleLanguages"]; [[NSUserDefaults standardUserDefaults] synchronize]; //to make the change immediate

Esto haría del alemán el idioma preferido para su aplicación, con el inglés y el francés como alternativa. Desearía llamar a esto en algún momento temprano en el inicio de su aplicación. Puede leer más sobre las preferencias de idioma / configuración regional aquí: Temas de programación de internacionalización: Obtención del idioma y la configuración regional actuales


NSLocalizedString() lee el valor de la clave AppleLanguages de los valores predeterminados de usuario estándar ( [NSUserDefaults standardUserDefaults] ). Utiliza ese valor para elegir una localización adecuada entre todas las localizaciones existentes en tiempo de ejecución. Cuando Apple crea el diccionario de valores por defecto del usuario al iniciar la aplicación, buscan la clave de idioma (s) preferido en las preferencias del sistema y copian el valor desde allí. Esto también explica, por ejemplo, por qué cambiar la configuración de idioma en OS X no tiene efecto en la ejecución de aplicaciones, solo en aplicaciones iniciadas a partir de entonces. Una vez copiado, el valor no se actualiza solo porque la configuración cambia. Es por eso que iOS reinicia todas las aplicaciones si cambias el idioma.

Sin embargo, todos los valores del diccionario de valores predeterminados del usuario se pueden sobrescribir con argumentos de la línea de comandos. Consulte la documentación de NSUserDefaults en NSArgumentDomain . Esto incluso incluye aquellos valores que se cargan desde el archivo de preferencias de la aplicación (.plist). Es realmente bueno saber si desea cambiar un valor solo una vez para la prueba .

Por lo tanto, si desea cambiar el idioma solo para las pruebas, es probable que no desee modificar su código (si olvida eliminar este código más adelante ...), en lugar de eso, indique a Xcode que inicie su aplicación con los parámetros de la línea de comandos ( por ejemplo, utilizar la localización en español):

No hay necesidad de tocar su código en absoluto. Simplemente cree diferentes esquemas para diferentes idiomas y puede iniciar rápidamente la aplicación una vez en un idioma y otra en otro simplemente cambiando el esquema.


Basado en la respuesta de Tudorizer para cambiar el idioma sin tener que abandonar o reiniciar la aplicación.

En lugar de una macro, use una clase para acceder al idioma preferido para verificar si existe un código de idioma específico.

A continuación se muestra una clase utilizada para obtener el paquete de idioma actual que funciona para iOS 9:

@implementation OSLocalization + (NSBundle *)currentLanguageBundle { // Default language incase an unsupported language is found NSString *language = @"en"; if ([NSLocale preferredLanguages].count) { // Check first object to be of type "en","es" etc // Codes seen by my eyes: "en-US","en","es-US","es" etc NSString *letterCode = [[NSLocale preferredLanguages] objectAtIndex:0]; if ([letterCode rangeOfString:@"en"].location != NSNotFound) { // English language = @"en"; } else if ([letterCode rangeOfString:@"es"].location != NSNotFound) { // Spanish language = @"es"; } else if ([letterCode rangeOfString:@"fr"].location != NSNotFound) { // French language = @"fr"; } // Add more if needed } return [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]]; } /// Check if preferred language is English + (BOOL)isCurrentLanguageEnglish { if (![NSLocale preferredLanguages].count) { // Just incase check for no items in array return YES; } if ([[[NSLocale preferredLanguages] objectAtIndex:0] rangeOfString:@"en"].location == NSNotFound) { // No letter code for english found return NO; } else { // Tis English return YES; } } /* Swap language between English & Spanish * Could send a string argument to directly pass the new language */ + (void)changeCurrentLanguage { if ([self isCurrentLanguageEnglish]) { [[NSUserDefaults standardUserDefaults] setObject:@[@"es"] forKey:@"AppleLanguages"]; } else { [[NSUserDefaults standardUserDefaults] setObject:@[@"en"] forKey:@"AppleLanguages"]; } } @end

Use la clase anterior para hacer referencia a un archivo de cadena / imagen / video / etc:

// Access a localized image [[OSLocalization currentLanguageBundle] pathForResource:@"my_image_name.png" ofType:nil] // Access a localized string from Localizable.strings file NSLocalizedStringFromTableInBundle(@"StringKey", nil, [OSLocalization currentLanguageBundle], @"comment")

Cambie el idioma en línea como abajo o actualice el método "changeCurrentLanguage" en la clase anterior para tomar un parámetro de cadena que haga referencia al nuevo idioma.

[[NSUserDefaults standardUserDefaults] setObject:@[@"es"] forKey:@"AppleLanguages"];


No lo use en iOS 9. Esto devuelve nil para todas las cadenas que pasan a través de él.

He encontrado otra solución que le permite actualizar las cadenas de idioma, sin reiniciar la aplicación y compatible con cadenas de caracteres:

Pon esta macro en el prefijo.pch:

#define currentLanguageBundle [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:[[NSLocale preferredLanguages] objectAtIndex:0] ofType:@"lproj"]]

y siempre que necesite un uso de cadena localizada:

NSLocalizedStringFromTableInBundle(@"GalleryTitleKey", nil, currentLanguageBundle, @"")

Para configurar el idioma de uso:

[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObject:@"de"] forKey:@"AppleLanguages"];

Funciona incluso con saltos de lenguaje 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, @""));


Swift 3 extensiones:

extension Locale { static var preferredLanguage: String { get { return self.preferredLanguages.first ?? "en" } set { UserDefaults.standard.set([newValue], forKey: "AppleLanguages") UserDefaults.standard.synchronize() } } } extension String { var localized: String { var result: String let languageCode = Locale.preferredLanguage //en-US var path = Bundle.main.path(forResource: languageCode, ofType: "lproj") if path == nil, let hyphenRange = languageCode.range(of: "-") { let languageCodeShort = languageCode.substring(to: hyphenRange.lowerBound) // en path = Bundle.main.path(forResource: languageCodeShort, ofType: "lproj") } if let path = path, let locBundle = Bundle(path: path) { result = locBundle.localizedString(forKey: self, value: nil, table: nil) } else { result = NSLocalizedString(self, comment: "") } return result } }

Uso:

Locale.preferredLanguage = "uk" label.text = "localizedKey".localized


Aquí hay una solución decente para este problema, y ​​no requiere reinicio de la aplicación.

https://github.com/cmaftuleac/BundleLocalization

Esta implementación funciona ajustando dentro de NSBundle. La idea es que anule el método localizedStringForKey en la instancia del objeto NSBundle y luego llame a este método en un paquete diferente con un idioma diferente. Sencillo y elegante totalmente compatible con todo tipo de recursos.


Esta función intentará obtener una cadena localizada para el idioma actual y, si no se encuentra, la obtendrá utilizando el idioma inglés.

- (NSString*)L:(NSString*)key { static NSString* valueNotFound = @"VALUE_NOT_FOUND"; static NSBundle* enBundle = nil; NSString* pl = [NSLocale preferredLanguages][0]; NSString* bp = [[NSBundle mainBundle] pathForResource:pl ofType:@"lproj"]; NSBundle* b = [NSBundle bundleWithPath:bp]; NSString* s = [b localizedStringForKey:key value:valueNotFound table:nil]; if ( [s isEqualToString:valueNotFound] ) { if ( !enBundle ) { bp = [[NSBundle mainBundle] pathForResource:@"en" ofType:@"lproj"]; enBundle = [NSBundle bundleWithPath:bp]; } s = [enBundle localizedStringForKey:key value:key table:nil]; } return s; }


haga lo que haga, la mejor manera es tomar el short_name para el idioma especificado, es decir: fr, en, nl, de, it, etc ... y asignar el mismo a un valor global.

cree una vista de selector para que aparezca como un menú desplegable (combinación de un botón al hacer clic en el que aparece una vista de selector desde abajo con una lista de idiomas) y seleccione el idioma que desee. Deja que el nombre corto se almacene internamente. haga un archivo .h + .m llamado LocalisedString.

Establezca el valor global de short_name para que sea igual al valor obtenido en LocalisedString.m Cuando se seleccione el idioma requerido, asigne NSBundlePath para crear subdirectorios del proyecto para el idioma necesario. por ejemplo, nl.proj, en.proj.

Cuando se selecciona la carpeta de proyecto particular, llame a la cadena localizada para el idioma respectivo y cambie el idioma dinámicamente.

no hay reglas rotas


Quería agregar soporte para un idioma que no es oficialmente compatible con iOS (no aparece en la sección Idioma en la configuración del sistema). Siguiendo el Tutorial de internacionalización de Apple y algunos consejos de Brian Webster y geon, se me ocurrió este código (póngalo en main.m):

int main(int argc, char * argv[]) { @autoreleasepool { // Grab regional settings locale, for Slovenian this is either sl_SI or en_SI NSLocale *locale = [NSLocale currentLocale]; NSString *ll = [locale localeIdentifier]; // sl_SI // Grab the first part of language identifier NSArray *comp = [ll componentsSeparatedByString:@"_"]; NSString *ll1 = @"en"; if (comp.count > 0) { ll1 = comp[0]; // sl, en, ... } // Check if we already saved language (user can manually change it inside app for example) if (![[NSUserDefaults standardUserDefaults] objectForKey:@"SelectedLanguage"]) { // Slovenian (Slovenia), Slovenia if ([ll isEqualToString:@"sl_SI"] || [ll isEqualToString:@"en_SI"]) { ll1 = @"sl-SI"; // This is the part of localized path for Slovenian language that Xcode generates } // Add more unsupported languages here... [[NSUserDefaults standardUserDefaults] setObject:ll1 forKey:@"SelectedLanguage"]; // Save language } else { // Restore language as we have previously saved it ll1 = [[NSUserDefaults standardUserDefaults] objectForKey:@"SelectedLanguage"]; } // Overwrite NSLocalizedString and StoryBoard language preference [[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:ll1, @"en", @"fr", nil] forKey:@"AppleLanguages"]; // Make sure settings are stored to disk [[NSUserDefaults standardUserDefaults] synchronize]; return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }

Esto funciona bien tanto para el código Storyboard como para el código NSLocalizedString. El código supone que el usuario tendrá la opción de cambiar manualmente el idioma dentro de la aplicación más adelante.

Por supuesto, no olvide agregar las traducciones correctas de Storyboard y las traducciones de Localizable.strings (vea el enlace a la página de Apple más arriba para saber cómo hacerlo).