web-dev-qa-db-fra.com

Meilleure pratique d'utilisation de NSLocalizedString

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 complétion automatique pour les chaînes dans Xcode. Cela rend le travail non seulement sujet aux erreurs, mais également 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.-à-d. "Entrez le mot de passe" par opposition à "Entrez le mot de passe en premier").
  • De la même manière que le problème d'auto-complétion, vous devez "vous souvenir"/copier les chaînes de commentaire, sinon genstring se retrouvera avec plusieurs commentaires pour une chaîne.
  • Si vous voulez utiliser genstring après avoir localisé certaines chaînes, vous devez faire attention à ne pas perdre vos anciennes localisations.
  • Les mêmes chaînes sont dispersées dans l'ensemble 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 font), c’est d’avoir un fichier strings.h séparé dans lequel je #define tout le code de localisation. 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 emplacement unique pour modifier les noms de variables (donc plus besoin de genstring) et un mot clé unique pour le refactorisation automatique. Toutefois, cela se traduit par un ensemble d'instructions #define qui ne sont pas structurées de manière inhérente (par exemple, comme LocString.Common.Cancel ou quelque chose comme ça). 

Ainsi, alors que cela fonctionne un peu bien, je me demandais comment vous le faisiez 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?

129
JiaYow

NSLocalizedString a quelques limitations, mais il est tellement central dans 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îne en supprimant toutes vos traductions précédentes . J'ai écrit update_strings.py pour analyser l'ancien fichier de chaînes, exécutez genstrings et remplissez les espaces afin que vous n'ayez pas à restaurer manuellement votre fichier existant. translations . Le script essaie de faire correspondre les fichiers de chaîne existants aussi étroitement que possible pour éviter d'avoir des différences trop grandes lors de leur mise à jour.

Nommer vos cordes

Si vous utilisez NSLocalizedString comme 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 avec le fait que le même terme anglais peut avoir une signification différente dans différents contextes (OK et Cancel viennent à l'esprit) . C'est pourquoi j'utilise toujours un mot qui n'a pas de sens Chaîne en majuscules 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 utiliser une macro comme vous le faites ou la mettre en cache en tant que variable d'instance dans votre contrôleur de vue ou votre source de données . De cette manière, vous n'aurez pas à répéter la description. peut devenir obsolète et devenir incohérent entre les instances de la même localisation, ce qui est toujours source de confusion . Comme les variables d’instance sont des symboles, vous pourrez utiliser la complétion automatique pour ces traductions les plus courantes et utiliser des chaînes "manuelles" pour la spécifiques, qui ne se produiraient qu'une fois de toute façon.

J'espère que vous serez plus productif avec la localisation de cacao grâce à ces conseils!

92
ndfred

Quant à la complétion automatique des chaînes dans Xcode, vous pouvez essayer http://questbe.at/lin/ .

30
hiroshi

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

Le second 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 comment 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éer 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 d’exécution dans vos phases de construction d’application afin que votre fichier Localizable.strings soit mis à jour à chaque génération, c’est-à-dire qu'une nouvelle chaîne localisée soit ajoutée à 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

Alors, quand vous ajoutez cette nouvelle ligne dans votre code:

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

Ensuite, effectuez une construction, votre fichier ./Localizable.scripts contiendra cette nouvelle ligne:

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

Et puisque clé == valeur pour 'view_settings_title', le LocalizedStringHandler personnalisé renvoie le commentaire, c'est-à-dire 'Paramètres ".

Voilà :-)

23
Pascal

J'ai écrit un script pour aider à maintenir Localizable.strings dans plusieurs langues. Bien que la complétion automatique ne soit pas utile, il est utile de fusionner des fichiers .strings à l'aide de la commande suivante:

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.

2
hiroshi

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 de value: pour la valeur de texte par défaut. Le premier paramètre sert d'identifiant de traduction. L'avantage d'utiliser le paramètre value: est que le texte par défaut peut être modifié ultérieurement, mais que l'ID de traduction reste le même. Le fichier Localizable.strings contiendra "btn_yes" = "Yes";

Si le paramètre value: 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 être étrange. Surtout si le texte traduit est long, l'identifiant l'est aussi. Chaque fois qu'un caractère de la valeur de texte par défaut est modifié, l'ID de traduction est également modifié. Cela pose des problèmes lorsque des systèmes de traduction externes sont utilisés. La modification de l'ID de traduction est comprise comme l'ajout d'un nouveau texte de traduction, ce qui peut ne pas être toujours souhaité.

1
petrsyn

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

Comme j'utilise ma propre macro sur NSLocalizedString, veuillez relire et mettre à jour le script avant d'utiliser car j'ai supposé par souci de simplicité que nil est utilisé comme second paramètre de NSLocalizedString. La partie que vous souhaitez changer est 

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

Il suffit de le remplacer par quelque chose qui correspond à votre macro et à votre utilisation de NSLocalizedString.

Voici le script, vous n’avez besoin que de la partie 3. Le reste est de voir plus facilement d'où ça 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 échantillon:

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

Certes, peut être plus poli, mais je pensais partager.

0

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

Avec quelques étapes à configurer, vous aurez une localisation très flexible dans Google Spreadsheet (commentaire, couleur personnalisée, mise en surbrillance, police, feuilles multiples, etc.).

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

En outre, il génère également Localizables.Swift, une structure qui agit comme une interface pour récupérer et décoder une clé (vous devez spécifier manuellement un moyen de décoder une chaîne à partir d'une clé).

Pourquoi est-ce génial?

  1. Vous n'avez plus besoin d'une clé sous forme de chaîne simple partout.
  2. De mauvaises clés sont détectées lors de la compilation.
  3. Xcode peut effectuer la 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 assurera que c'est toujours une clé valide, sinon elle ne sera pas compilée.

// 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 les feuilles -> CSV et le script Python pour convertir les fichiers CSV -> Localizable.strings. Vous pouvez consulter rapidement cet exemple de feuille pour savoir ce qui est possible.

0
aunnnn
#define PBLocalizedString(key, val) \

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

avec iOS 7 et Xcode 5, évitez d'utiliser la méthode 'Localization.strings' et utilisez la nouvelle méthode 'localisation de base'. Il existe des tutoriels si vous recherchez une "localisation de base" sur Google.

Apple doc: Localisation de la base

0
Ronny Webers