Meilleures pratiques d'utilisation de NSLocalizedString

140

J'utilise (comme tous les autres) NSLocalizedStringpour localiser mon application.

Malheureusement, il existe plusieurs «inconvénients» (pas nécessairement la faute de NSLocalizedString lui-même), y compris

  • Pas de saisie semi-automatique pour les chaînes dans Xcode. Cela rend le travail non seulement sujet aux erreurs, mais aussi fastidieux.
  • Vous pourriez finir par redéfinir une chaîne simplement parce que vous ne saviez pas qu'une chaîne équivalente existait déjà (c'est-à-dire «Veuillez saisir le mot de passe» ou «Saisissez d'abord le mot de passe»)
  • De la même manière que pour le problème de la saisie semi-automatique, vous devez "vous souvenir" / copier-coller les chaînes de commentaires, sinon vous genstringobtiendrez plusieurs commentaires pour une chaîne
  • Si vous souhaitez utiliser genstringaprès avoir déjà localisé certaines chaînes, vous devez faire attention à ne pas perdre vos anciennes localisations.
  • Les mêmes chaînes sont dispersées tout au long de votre projet. Par exemple, vous avez utilisé NSLocalizedString(@"Abort", @"Cancel action")partout, puis Code Review vous demande de renommer la chaîne en NSLocalizedString(@"Cancel", @"Cancel action")pour rendre le code plus cohérent.

Ce que je fais (et après quelques recherches sur SO, je me suis dit que beaucoup de gens le faisaient), c'est d'avoir un strings.hfichier séparé où je #definelocalisais tout le code. Par exemple

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

Cela fournit essentiellement la complétion de code, un endroit unique pour changer les noms de variables (donc plus besoin de genstring) et un mot clé unique pour l'auto-refactorisation. Cependant, cela se fait au prix de se retrouver avec tout un tas d' #defineinstructions qui ne sont pas intrinsèquement structurées (c'est-à-dire comme LocString.Common.Cancel ou quelque chose comme ça).

Donc, bien que cela fonctionne plutôt bien, je me demandais comment vous le faites dans vos projets. Existe-t-il d'autres approches pour simplifier l'utilisation de NSLocalizedString? Y a-t-il peut-être même un cadre qui l'encapsule?

JiaYow
la source
Je fais juste presque la même chose comme toi. Mais j'utilise le makro NSLocalizedStringWithDefaultValue pour créer différents fichiers de chaînes pour différents problèmes de localisation (comme des contrôleurs, des modèles, etc.) et pour créer une valeur par défaut initiale.
anka
Il semble que l'exportation vers la localisation de xcode6 n'attrape pas les chaînes définies comme macros dans un fichier d'en-tête. Quelqu'un peut-il me confirmer ou me dire ce que je pourrais manquer? Merci...!
Juddster
@Juddster, peut confirmer, même avec le nouvel éditeur de fonds-> Exporter pour la localisation, il n'est pas repris dans le fichier d'en-tête
Rouge

Réponses:

100

NSLocalizedStringa quelques limitations, mais il est si central pour Cocoa qu'il est déraisonnable d'écrire du code personnalisé pour gérer la localisation, ce qui signifie que vous devrez l'utiliser. Cela dit, un petit outillage peut aider, voici comment je procède:

Mise à jour du fichier de chaînes

genstringsécrase vos fichiers de chaînes, supprimant toutes vos traductions précédentes. J'ai écrit update_strings.py pour analyser l'ancien fichier de chaînes, exécuter genstringset remplir les blancs afin que vous n'ayez pas à restaurer manuellement vos traductions existantes. Le script essaie de faire correspondre le plus possible les fichiers de chaînes existants pour éviter d'avoir une différence trop importante lors de leur mise à jour.

Nommer vos chaînes

Si vous utilisez NSLocalizedStringcomme annoncé:

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

Vous pouvez finir par définir la même chaîne dans une autre partie de votre code, ce qui peut entrer en conflit car le même terme anglais peut avoir une signification différente dans différents contextes ( OKet cela vous Cancelvient à l'esprit). C'est pourquoi j'utilise toujours une chaîne en majuscules sans signification avec un préfixe spécifique au module et une description très précise:

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

Utiliser la même chaîne à différents endroits

Si vous utilisez la même chaîne plusieurs fois, vous pouvez soit utiliser une macro comme vous l'avez fait, soit la mettre en cache en tant que variable d'instance dans votre contrôleur de vue ou votre source de données. De cette façon, vous n'aurez pas à répéter la description qui peut devenir obsolète et devenir incohérente entre les instances de la même localisation, ce qui est toujours déroutant. Comme les variables d'instance sont des symboles, vous pourrez utiliser l'auto-complétion sur ces traductions les plus courantes, et utiliser des chaînes "manuelles" pour celles spécifiques, ce qui ne se produirait qu'une seule fois de toute façon.

J'espère que vous serez plus productif avec la localisation de Cocoa avec ces conseils!

ndfred
la source
Merci pour votre réponse, je vais certainement jeter un œil à votre fichier python. Je suis d'accord avec vos conventions de dénomination. J'ai récemment discuté avec d'autres développeurs iOS et ils ont recommandé l'utilisation de chaînes statiques au lieu de macros, ce qui est logique. J'ai voté pour votre réponse, mais j'attendrai un peu avant de l'accepter, car la solution est encore un peu maladroite. Peut-être que quelque chose de mieux arrive. Merci encore!
JiaYow
Vous êtes les bienvenus. La localisation est un processus fastidieux, disposer des bons outils et flux de travail fait toute la différence.
ndfred
17
Je n'ai jamais compris pourquoi les fonctions de localisation de style gettext utilisent l'une des traductions comme clé. Que se passe-t-il si votre texte d'origine change? Vos modifications de clé et tous vos fichiers localisés utilisent l'ancien texte pour leur clé. Cela n'a jamais eu de sens pour moi. J'ai toujours utilisé des clés comme "home_button_text" pour qu'elles soient uniques et ne changent jamais. J'ai également écrit un script bash pour analyser tous mes fichiers Localizable.strings et générer un fichier de classe avec des méthodes statiques qui chargeront la chaîne appropriée. Cela me donne l'achèvement du code. Un jour, je pourrais ouvrir ce logiciel.
Mike Weller
2
Je pense que tu genstringsne veux pas dire gestring.
hiroshi
1
@ndfred lors de la compilation vérifie que vous n'avez pas mal tapé la chaîne est la plus grande victoire. C'est un peu plus de code à ajouter de toute façon. Aussi dans le cas du refactoring, de l'analyse statique, avoir un symbole va rendre les choses plus faciles.
Allen Zeng
31

En ce qui concerne la saisie semi-automatique des chaînes dans Xcode, vous pouvez essayer https://github.com/questbeat/Lin .

Hiroshi
la source
3
C'est en fait assez étonnant. Pas besoin de créer des macros.
Beau Nouvelle
1
page non trouvée_
Juanmi
1
@Juanmi Merci d'avoir mentionné le lien mort. J'ai remplacé le lien par github url.
hiroshi
24

D'accord avec ndfred, mais j'aimerais ajouter ceci:

Le deuxième paramètre peut être utilisé comme ... valeur par défaut !!

(NSLocalizedStringWithDefaultValue ne fonctionne pas correctement avec genstring, c'est pourquoi j'ai proposé cette solution)

Voici mon implémentation personnalisée qui utilise NSLocalizedString qui utilise le commentaire comme valeur par défaut:

1 . Dans votre en-tête pré-compilé (fichier .pch), redéfinissez 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. créez une classe pour implémenter le gestionnaire de localisation

#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. Utilisez-le!

Assurez-vous d'ajouter un script Run dans vos phases de construction d'application afin que votre fichier Localizable.strings soit mis à jour à chaque construction, c'est-à-dire qu'une nouvelle chaîne localisée sera ajoutée dans votre fichier Localized.strings:

Mon script de phase de construction est un script shell:

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

Ainsi, lorsque vous ajoutez cette nouvelle ligne dans votre code:

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

Puis effectuez une compilation, votre fichier ./Localizable.scripts contiendra cette nouvelle ligne:

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

Et puisque la clé == valeur pour 'view_settings_title', le LocalizedStringHandler personnalisé retournera le commentaire, c'est-à-dire 'Settings'

Voilà :-)

Pascal
la source
Obtention d'erreurs ARC, aucune méthode d'instance connue pour le sélecteur 'localizedString: comment: :(
Mangesh
Je suppose que c'est parce que LocalizationHandlerUtil.h est manquant. Je ne trouve pas le code en arrière ... Essayez simplement de créer le fichier d'en-tête LocalizationHandlerUtil.h et ça devrait être OK
Pascal
J'ai créé les fichiers. Je pense que cela est dû à un problème de chemin de dossier.
Mangesh
3

Dans Swift, j'utilise ce qui suit, par exemple pour le bouton "Oui" dans ce cas:

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

Notez l'utilisation du value:pour la valeur de texte par défaut. Le premier paramètre sert d'ID de traduction. L'avantage d'utiliser levalue: paramètre est que le texte par défaut peut être modifié ultérieurement, mais l'ID de traduction reste le même. Le fichier Localizable.strings contiendra"btn_yes" = "Yes";

Si le value:paramètre n'était pas utilisé, le premier paramètre serait utilisé pour les deux: pour l'ID de traduction et également pour la valeur de texte par défaut. Le fichier Localizable.strings contiendrait "Yes" = "Yes";. Ce type de gestion des fichiers de localisation semble étrange. Surtout si le texte traduit est long, l'ID l'est également. Chaque fois qu'un caractère de la valeur de texte par défaut est modifié, l'ID de traduction est également modifié. Cela conduit à des problèmes lorsque des systèmes de traduction externes sont utilisés. Le changement de l'ID de traduction signifie l'ajout d'un nouveau texte de traduction, ce qui n'est pas toujours souhaité.

Petrsyn
la source
2

J'ai écrit un script pour aider à maintenir Localizable.strings dans plusieurs langues. Bien que cela n'aide pas à la saisie semi-automatique, il est utile de fusionner les fichiers .strings à l'aide de la commande:

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

Pour plus d'informations, voir: https://github.com/hiroshi/merge_strings

Certains d'entre vous le trouvent utile j'espère.

Hiroshi
la source
2

Si quelqu'un cherche une solution Swift. Vous voudrez peut-être consulter ma solution que j'ai mise en place ici: SwiftyLocalization

En quelques étapes de configuration, vous aurez une localisation très flexible dans Google Spreadsheet (commentaire, couleur personnalisée, surbrillance, police, plusieurs feuilles, etc.).

En bref, les étapes sont: Google Spreadsheet -> Fichiers CSV -> Localizable.strings

De plus, il génère également Localizables.swift, une structure qui agit comme des interfaces pour une récupération et un décodage de clé pour vous (vous devez cependant spécifier manuellement un moyen de décoder la chaîne à partir de la clé).

Pourquoi est-ce génial?

  1. Vous n'avez plus besoin d'avoir une clé sous forme de chaîne simple partout.
  2. Des clés incorrectes sont détectées au moment de la compilation.
  3. Xcode peut effectuer une saisie semi-automatique.

Bien qu'il existe des outils qui peuvent compléter automatiquement votre clé localisable. La référence à une variable réelle garantira qu'il s'agit toujours d'une clé valide, sinon elle ne se compilera pas.

// 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)

Le projet utilise Google App Script pour convertir Sheets -> CSV et Python script pour convertir des fichiers CSV -> Localizable.strings Vous pouvez jeter un coup d'œil à cette feuille d'exemple pour savoir ce qui est possible.

aunnnn
la source
1

avec iOS 7 et Xcode 5, vous devez éviter d'utiliser la méthode «Localization.strings» et utiliser la nouvelle méthode de «localisation de base». Il existe des didacticiels si vous recherchez sur Google la `` localisation de base ''

Doc Apple: localisation de la base

Ronny Webers
la source
oui Steve c'est correct. En outre, vous avez toujours besoin de la méthode de fichier .strings pour toute chaîne générée dynamiquement. Mais uniquement pour ceux-ci, la méthode préférée d'Apple est la localisation de base.
Ronny Webers
Lien vers la nouvelle méthode?
Hyperbole
1
Imo, la méthode de localisation de base est sans valeur. Vous devez toujours conserver d'autres fichiers d'emplacement pour les chaînes dynamiques, et cela maintient vos chaînes réparties dans un grand nombre de fichiers. Les chaînes à l'intérieur des Nibs / Storyboards peuvent être localisées automatiquement sur les clés de Localizable.strings avec certaines librairies telles que github.com/AliSoftware/OHAutoNIBi18n
Rafael Nobre
0
#define PBLocalizedString(key, val) \

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

Moi-même, je suis souvent emporté par le codage, oubliant de mettre les entrées dans des fichiers .strings. Ainsi, j'ai des scripts d'aide pour trouver ce que je dois remettre dans des fichiers .strings et traduire.

Comme j'utilise ma propre macro sur NSLocalizedString, veuillez revoir et mettre à jour le script avant de l'utiliser comme je l'ai supposé pour plus de simplicité que nul est utilisé comme second paramètre de NSLocalizedString. La partie que vous voudriez changer est

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

Remplacez-le simplement par quelque chose qui correspond à votre macro et à l'utilisation de NSLocalizedString.

Voici le script, vous n'avez besoin que de la partie 3. Le reste est de voir plus facilement d'où tout cela vient:

// 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

Le fichier de sortie contient des clés trouvées dans le code, mais pas dans le fichier Localizable.strings. Voici un exemple:

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

Peut-être certainement plus poli, mais je pensais que je partagerais.

Stanislav Dvoychenko
la source