Mejores prácticas usando NSLocalizedString

140

Estoy (como todos los demás) usando NSLocalizedStringpara localizar mi aplicación.

Desafortunadamente, hay varios "inconvenientes" (no necesariamente por culpa de NSLocalizedString), incluyendo

  • No hay autocompletado para cadenas en Xcode. Esto hace que trabajar no solo sea propenso a errores, sino también pesado.
  • Podría terminar redefiniendo una cadena simplemente porque no sabía que ya existía una cadena equivalente (es decir, "Ingrese la contraseña" frente a "Ingrese la contraseña primero")
  • De manera similar al problema de autocompletado, debe "recordar" / copiar las cadenas de comentarios, o de lo contrario genstringterminará con múltiples comentarios para una cadena
  • Si desea usarlo genstringdespués de haber localizado algunas cadenas, debe tener cuidado de no perder sus antiguas localizaciones.
  • Las mismas cadenas están dispersas en todo su proyecto. Por ejemplo, usó en NSLocalizedString(@"Abort", @"Cancel action")todas partes, y luego Code Review le pide que cambie el nombre de la cadena NSLocalizedString(@"Cancel", @"Cancel action")para que el código sea más coherente.

Lo que hago (y después de algunas búsquedas en SO me di cuenta de que muchas personas hacen esto) es tener un strings.harchivo separado en #defineel que localizo todo el código. Por ejemplo

// In strings.h
#define NSLS_COMMON_CANCEL NSLocalizedString(@"Cancel", nil)
// Somewhere else
NSLog(@"%@", NSLS_COMMON_CANCEL);

Básicamente, esto proporciona la finalización del código, un lugar único para cambiar los nombres de las variables (por lo que ya no es necesario usar genstring) y una palabra clave única para auto-refactorizar. Sin embargo, esto tiene el costo de terminar con un montón de #definedeclaraciones que no están inherentemente estructuradas (es decir, como LocString.Common.Cancel o algo así).

Entonces, aunque esto funciona bastante bien, me preguntaba cómo lo hacen en sus proyectos. ¿Existen otros enfoques para simplificar el uso de NSLocalizedString? ¿Existe quizás un marco que lo encapsule?

JiaYow
fuente
Simplemente lo hago casi igual que tú. Pero estoy usando el makro NSLocalizedStringWithDefaultValue para crear diferentes archivos de cadenas para diferentes problemas de localización (como controladores, modelos, etc.) y para crear un valor predeterminado inicial.
anka
Parece que la exportación a localización de xcode6 no captura las cadenas que se definen como macros en un archivo de encabezado. ¿Alguien puede confirmar o decirme lo que podría estar perdiendo? Gracias...!
Juddster
@Juddster, puede confirmar, incluso con el nuevo Editor de fondos-> Exportar para localización, no se recoge en el archivo de encabezado
Rojo

Respuestas:

100

NSLocalizedStringtiene algunas limitaciones, pero es tan central para Cocoa que no es razonable escribir código personalizado para manejar la localización, lo que significa que tendrá que usarlo. Dicho esto, un poco de herramientas pueden ayudar, así es como procedo:

Actualizando el archivo de cadenas

genstringssobrescribe sus archivos de cadena, descartando todas sus traducciones anteriores. Escribí update_strings.py para analizar el viejo archivo de cadenas, ejecutar genstringsy completar los espacios en blanco para que no tenga que restaurar manualmente sus traducciones existentes. El script intenta hacer coincidir los archivos de cadena existentes lo más cerca posible para evitar tener una diferencia demasiado grande al actualizarlos.

Nombrando tus cuerdas

Si usa NSLocalizedStringcomo se anuncia:

NSLocalizedString(@"Cancel or continue?", @"Cancel notice message when a download takes too long to proceed");

Puede terminar definiendo la misma cadena en otra parte de su código, lo que puede entrar en conflicto ya que el mismo término en inglés puede tener un significado diferente en diferentes contextos ( OKy Cancelviene a la mente). Es por eso que siempre uso una cadena de mayúsculas sin sentido con un prefijo específico del módulo, y una descripción muy precisa:

NSLocalizedString(@"DOWNLOAD_CANCEL_OR_CONTINUE", @"Cancel notice window title when a download takes too long to proceed");

Usando la misma cuerda en diferentes lugares

Si usa la misma cadena varias veces, puede usar una macro como lo hizo o almacenarla en caché como una variable de instancia en su controlador de vista o su fuente de datos. De esta manera, no tendrá que repetir la descripción, que puede quedar obsoleta y ser inconsistente entre las instancias de la misma localización, lo que siempre es confuso. Como las variables de instancia son símbolos, podrá utilizar el autocompletado en estas traducciones más comunes y utilizar cadenas "manuales" para las específicas, que de todos modos solo ocurrirían una vez.

¡Espero que seas más productivo con la localización del cacao con estos consejos!

ndfred
fuente
Gracias por su respuesta, definitivamente echaré un vistazo a su archivo python. Estoy de acuerdo con tus convenciones de nombres. Recientemente he hablado con otros desarrolladores de iOS y me recomendaron el uso de cadenas estáticas en lugar de macros, lo cual tiene sentido. He votado tu respuesta, pero esperaré un poco antes de aceptarla, porque la solución aún es un poco torpe. Quizás aparezca algo mejor. ¡Gracias de nuevo!
JiaYow
Eres muy bienvenido. La localización es un proceso tedioso, tener las herramientas y el flujo de trabajo adecuados hace una gran diferencia.
ndfred
17
Nunca he entendido por qué las funciones de localización al estilo gettext usan una de las traducciones como clave. ¿Qué sucede si tu texto original cambia? Su clave cambia y todos sus archivos localizados están utilizando el texto antiguo para su clave. Nunca ha tenido sentido para mí. Siempre he usado claves como "home_button_text" para que sean únicas y nunca cambien. También he escrito un script bash para analizar todos mis archivos Localizable.strings y generar un archivo de clase con métodos estáticos que cargará la cadena adecuada. Esto me da la finalización del código. Algún día podría abrir el código esto.
Mike Weller
2
Creo que quieres decir que genstringsno gestring.
hiroshi
1
@ndfred el tiempo de compilación comprueba que no ha escrito mal la cadena es la mayor victoria. Es marginalmente más código para agregar de todos modos. También en el caso de la refactorización, el análisis estático, tener un símbolo hará que todo sea mucho más fácil.
Allen Zeng
31

En cuanto a la autocompletado de cadenas en Xcode, puede probar https://github.com/questbeat/Lin .

Hiroshi
fuente
3
Esto es realmente bastante sorprendente. No es necesario crear macros.
Beau Nouvelle
1
página no encontrada_
Juanmi
1
@Juanmi Gracias por mencionar el enlace muerto. Reemplacé el enlace con github url.
hiroshi
24

De acuerdo con ndfred, pero me gustaría agregar esto:

El segundo parámetro se puede usar como ... ¡valor predeterminado!

(NSLocalizedStringWithDefaultValue no funciona correctamente con genstring, por eso propuse esta solución)

Aquí está mi implementación personalizada que usa NSLocalizedString que usa el comentario como valor predeterminado:

1) En su encabezado precompilado (archivo .pch), redefina la macro 'NSLocalizedString':

// cutom NSLocalizedString that use macro comment as default value
#import "LocalizationHandlerUtil.h"

#undef NSLocalizedString
#define NSLocalizedString(key,_comment) [[LocalizationHandlerUtil singleton] localizedString:key  comment:_comment]

2. crear una clase para implementar el controlador de localización

#import "LocalizationHandlerUtil.h"

@implementation LocalizationHandlerUtil

static LocalizationHandlerUtil * singleton = nil;

+ (LocalizationHandlerUtil *)singleton
{
    return singleton;
}

__attribute__((constructor))
static void staticInit_singleton()
{
    singleton = [[LocalizationHandlerUtil alloc] init];
}

- (NSString *)localizedString:(NSString *)key comment:(NSString *)comment
{
    // default localized string loading
    NSString * localizedString = [[NSBundle mainBundle] localizedStringForKey:key value:key table:nil];

    // if (value == key) and comment is not nil -> returns comment
    if([localizedString isEqualToString:key] && comment !=nil)
        return comment;

    return localizedString;
}

@end

3. ¡Úselo!

Asegúrese de agregar un script de ejecución en sus fases de compilación de aplicaciones para que su archivo Localizable.strings se actualice en cada compilación, es decir, se agregará una nueva cadena localizada en su archivo Localized.strings:

Mi script de fase de compilación es un script de shell:

Shell: /bin/sh
Shell script content: find . -name \*.m | xargs genstrings -o MyClassesFolder

Entonces, cuando agrega esta nueva línea en su código:

self.title = NSLocalizedString(@"view_settings_title", @"Settings");

Luego realice una compilación, su archivo ./Localizable.scripts contendrá esta nueva línea:

/* Settings */
"view_settings_title" = "view_settings_title";

Y dado que key == value para 'view_settings_title', el LocalizedStringHandler personalizado devolverá el comentario, es decir, 'Configuración "

Voilà :-)

Pascal
fuente
Obteniendo errores de ARC, no se conoce ningún método de instancia para el selector 'localizedString: comentario: :(
Mangesh
Supongo que es porque falta LocalizationHandlerUtil.h. No puedo encontrar el código de nuevo ... Solo intenta crear el archivo de encabezado LocalizationHandlerUtil.h y debería estar bien
Pascal
He creado los archivos. Creo que se debe a un problema de ruta de carpeta.
Mangesh
3

En Swift estoy usando lo siguiente, por ejemplo, para el botón "Sí" en este caso:

NSLocalizedString("btn_yes", value: "Yes", comment: "Yes button")

Tenga en cuenta el uso de value:para el valor de texto predeterminado. El primer parámetro sirve como ID de traducción. La ventaja de usar el value:parámetro es que el texto predeterminado se puede cambiar más tarde, pero la ID de la traducción sigue siendo la misma. El archivo Localizable.strings contendrá"btn_yes" = "Yes";

Si value:no se usó el parámetro, el primer parámetro se usaría para ambos: para la ID de traducción y también para el valor de texto predeterminado. El archivo Localizable.strings contendría "Yes" = "Yes";. Este tipo de archivos de localización de gestión parece ser extraño. Especialmente si el texto traducido es largo, la ID también lo es. Cada vez que se cambia cualquier carácter del valor de texto predeterminado, también se cambia la ID de traducción. Esto lleva a problemas cuando se utilizan sistemas de traducción externos. El cambio de la ID de traducción se entiende como la adición de un nuevo texto de traducción, que no siempre es deseable.

petrsyn
fuente
2

Escribí un script para ayudar a mantener Localizable.strings en varios idiomas. Si bien no ayuda en el autocompletado, ayuda a fusionar archivos .strings usando el comando:

merge_strings.rb ja.lproj/Localizable.strings en.lproj/Localizable.strings

Para obtener más información, consulte: https://github.com/hiroshi/merge_strings

Algunos de ustedes lo encuentran útil, espero.

Hiroshi
fuente
2

Si alguien busca una solución rápida. Es posible que desee ver mi solución que reuní aquí: SwiftyLocalization

Con unos pocos pasos para la configuración, tendrá una localización muy flexible en la hoja de cálculo de Google (comentario, color personalizado, resaltado, fuente, varias hojas y más).

En resumen, los pasos son: Hoja de cálculo de Google -> archivos CSV -> Localizable.strings

Además, también genera Localizables.swift, una estructura que actúa como interfaces para una recuperación y decodificación de claves para usted (sin embargo, debe especificar manualmente una forma de decodificar String desde la clave).

¿Por qué es esto genial?

  1. Ya no necesita tener una clave como una cadena simple en todos los lugares.
  2. Se detectan claves incorrectas en tiempo de compilación.
  3. Xcode puede hacer autocompletar.

Si bien hay herramientas que pueden completar automáticamente su clave localizable. La referencia a una variable real asegurará que siempre sea una clave válida, de lo contrario no se compilará.

// It's defined as computed static var, so it's up-to-date every time you call. 
// You can also have your custom retrieval method there.

button.setTitle(Localizables.login.button_title_login, forState: .Normal)

El proyecto utiliza Google App Script para convertir Hojas -> CSV, y Python script para convertir archivos CSV -> Localizable.strings Puede echar un vistazo rápido a esta hoja de ejemplo para saber qué es posible.

aunnnn
fuente
1

con iOS 7 y Xcode 5, debe evitar usar el método 'Localization.strings' y usar el nuevo método 'base localization'. Hay algunos tutoriales disponibles si buscas en Google para 'localización de base'

Apple doc: localización de bases

Ronny Webers
fuente
Sí Steve, eso es correcto. Además, aún necesita el método de archivo .strings para cualquier cadena generada dinámicamente. Pero solo para estos, el método preferido de Apple es la localización de bases.
Ronny Webers
Enlace al nuevo método?
Hipérbole
1
Imo, el método de localización base no tiene valor. Todavía tiene que guardar otros archivos de ubicación para cadenas dinámicas, y mantiene sus cadenas extendidas a través de muchos archivos. Las cadenas dentro de Nibs / Storyboards pueden localizarse automáticamente en teclas en Localizable.strings con algunas Libs como github.com/AliSoftware/OHAutoNIBi18n
Rafael Nobre
0
#define PBLocalizedString(key, val) \

[[NSBundle mainBundle] localizedStringForKey:(key) value:(val) table:nil]
baozhifei
fuente
0

Yo mismo, a menudo me dejo llevar por la codificación, olvidando poner las entradas en archivos .strings. Por lo tanto, tengo scripts de ayuda para encontrar qué debo devolver a los archivos .strings y traducir.

Como uso mi propia macro sobre NSLocalizedString, revise y actualice el script antes de usar, ya que supuse por simplicidad que nil se usa como segundo parámetro de NSLocalizedString. La parte que te gustaría cambiar es

NSLocalizedString\(@(".*?")\s*,\s*nil\) 

Simplemente reemplácelo con algo que coincida con su uso de macro y NSLocalizedString.

Aquí viene el script, solo necesitas la Parte 3 de hecho. El resto es ver más fácilmente de dónde viene todo:

// Part 1. Get keys from one of the Localizable.strings
perl -ne 'print "$1\n" if /^\s*(".+")\s*=/' myapp/fr.lproj/Localizable.strings

// Part 2. Get keys from the source code
grep -n -h -Eo -r  'NSLocalizedString\(@(".*?")\s*,\s*nil\)' ./ | perl -ne 'print "$1\n" if /NSLocalizedString\(@(".+")\s*,\s*nil\)/'

// Part 3. Get Part 1 and 2 together.

comm -2 -3 <(grep -n -h -Eo -r  'NSLocalizedString\(@(".*?")\s*,\s*nil\)' ./ | perl -ne 'print "$1\n" if /NSLocalizedString\(@(".+")\s*,\s*nil\)/' | sort | uniq) <(perl -ne 'print "$1\n" if /^\s*(".+")\s*=/' myapp/fr.lproj/Localizable.strings | sort) | uniq >> fr-localization-delta.txt

El archivo de salida contiene claves que se encontraron en el código, pero no en el archivo Localizable.strings. Aquí hay una muestra:

"MPH"
"Map Direction"
"Max duration of a detailed recording, hours"
"Moving ..."
"My Track"
"New Trip"

Ciertamente se puede pulir más, pero pensé que lo compartiría.

Stanislav Dvoychenko
fuente