web-dev-qa-db-fra.com

Localisation d'interpolation de chaîne C # 6.0

C # 6.0 a une interpolation de chaîne - une fonctionnalité intéressante pour formater des chaînes comme:

 var name = "John";
 WriteLine($"My name is {name}");

L'exemple est converti en

 var name = "John";
 WriteLine(String.Format("My name is {0}", name));

Du point de vue de la localisation, il est préférable de stocker des chaînes comme:

"My name is {name} {middlename} {surname}" 

qu'en notation String.Format:

"My name is {0} {1} {2}"

Comment utiliser l'interpolation de chaîne pour la localisation .NET? Y aura-t-il un moyen de mettre $ "..." dans les fichiers de ressources? Ou les chaînes doivent-elles être stockées comme "... {nom}" et en quelque sorte interpolées à la volée?

P.S. Cette question ne concerne PAS "comment faire une extension string.FormatIt" (il y a BEAUCOUP de telles bibliothèques, SO réponses, etc.). Cette question concerne quelque chose comme l'extension Roslyn pour "string interpolation "dans le contexte de" localisation "(les deux sont des termes du vocabulaire MS .NET), ou utilisation dynamique comme Dylan l'a proposé.

65
MajesticRa

En utilisant le package Microsoft.CodeAnalysis.CSharp.Scripting vous pouvez y parvenir.

Vous devrez créer un objet dans lequel stocker les données, sous un objet dynamique est utilisé. Vous pouvez également créer une classe spécifique avec toutes les propriétés requises. La raison pour encapsuler l'objet dynamique dans une classe est décrite ici .

public class DynamicData
{
    public dynamic Data { get; } = new ExpandoObject();
}

Vous pouvez ensuite l'utiliser comme indiqué ci-dessous.

var options = ScriptOptions.Default
    .AddReferences(
        typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).GetTypeInfo().Assembly,
        typeof(System.Runtime.CompilerServices.DynamicAttribute).GetTypeInfo().Assembly);

var globals = new DynamicData();
globals.Data.Name = "John";
globals.Data.MiddleName = "James";
globals.Data.Surname = "Jamison";

var text = "My name is {Data.Name} {Data.MiddleName} {Data.Surname}";
var result = await CSharpScript.EvaluateAsync<string>($"$\"{text}\"", options, globals);

Il s'agit de compiler l'extrait de code et de l'exécuter, il s'agit donc d'une véritable interpolation de chaîne C #. Bien que vous deviez prendre en compte les performances de celui-ci car il compile et exécute réellement votre code au moment de l'exécution. Pour contourner ces performances, vous pouvez utiliser CSharpScript.Create pour compiler et mettre en cache le code.

3
Dylan

Une chaîne interpolée évalue le bloc entre les accolades comme une expression C # (par exemple {expression}, {1 + 1}, {person.FirstName}).

Cela signifie que les expressions d'une chaîne interpolée doivent faire référence à des noms dans le contexte actuel.

Par exemple, cette instruction ne compilera pas:

var nameFormat = $"My name is {name}"; // Cannot use *name*
                                       // before it is declared
var name = "Fred";
WriteLine(nameFormat);

De même:

class Program
{
    const string interpolated = $"{firstName}"; // Name *firstName* does not exist
                                                // in the current context
    static void Main(string[] args)
    {
        var firstName = "fred";
        Console.WriteLine(interpolated);
        Console.ReadKey();
    }
}

Pour répondre à ta question:

Il n'y a aucun mécanisme actuel fourni par le framework pour évaluer les chaînes interpolées au moment de l'exécution. Par conséquent, vous ne pouvez pas stocker de chaînes et interpoler à la volée hors de la boîte.

Il existe des bibliothèques qui gèrent l'interpolation d'exécution des chaînes.

35
Dustin Kingen

Selon cette discussion sur le site du codeplex de Roslyn, l'interpolation de chaînes ne sera probablement pas compatible avec les fichiers de ressources (c'est moi qui souligne):

L'interpolation de chaînes pourrait être plus nette et plus facile à déboguer que String.Format ou la concaténation ...

Dim y = $"Robot {name} reporting
{coolant.name} levels are {coolant.level}
{reactor.name} levels are {reactor.level}"

Cependant, cet exemple est louche. La plupart des programmeurs professionnels n'écriront pas de chaînes orientées utilisateur dans le code. Au lieu de cela, ils stockeront ces chaînes dans des ressources (.resw, .resx ou .xlf) pour des raisons de localisation. Il ne semble donc pas très utile ici d'interpoler des chaînes.

15
BJ Myers

Comme déjà dit dans les réponses précédentes: vous ne pouvez actuellement pas charger la chaîne de formatage lors de l'exécution (par exemple à partir de fichiers de ressources) pour l'interpolation de chaînes car elle est utilisée au moment de la compilation.

Si vous ne vous souciez pas de la fonctionnalité de compilation et souhaitez simplement avoir des espaces réservés nommés, vous pouvez utiliser quelque chose comme cette méthode d'extension:

public static string StringFormat(this string input, Dictionary<string, object> elements)
{
    int i = 0;
    var values = new object[elements.Count];
    foreach (var elem in elements)
    {
        input = Regex.Replace(input, "{" + Regex.Escape(elem.Key) + "(?<format>[^}]+)?}", "{" + i + "${format}}");
        values[i++] = elem.Value;
    }
    return string.Format(input, values);
}

Sachez que vous ne pouvez pas avoir d'expressions en ligne comme {i+1} ici et que ce n'est pas du code avec les meilleures performances.

Vous pouvez l'utiliser avec un dictionnaire que vous chargez à partir de fichiers de ressources ou en ligne comme ceci:

var txt = "Hello {name} on {day:yyyy-MM-dd}!".StringFormat(new Dictionary<string, object>
            {
                ["name"] = "Joe",
                ["day"] = DateTime.Now,
            });
5
stb

En supposant que votre question porte davantage sur la façon de localiser des chaînes interpolées dans votre code source, et non sur la façon de gérer les ressources de chaînes interpolées ...

Étant donné l'exemple de code:

var name = "John";
var middlename = "W";
var surname = "Bloggs";
var text = $"My name is {name} {middlename} {surname}";
Console.WriteLine(text);

La sortie est évidemment:

My name is John W Bloggs

Modifiez maintenant l'affectation du texte pour récupérer une traduction à la place:

var text = Translate($"My name is {name} {middlename} {surname}");

Translate est implémenté comme ceci:

public static string Translate(FormattableString text)
{
    return string.Format(GetTranslation(text.Format),
        text.GetArguments());
}

private static string GetTranslation(string text)
{
    return text; // actually use gettext or whatever
}

Vous devez fournir votre propre implémentation de GetTranslation; il recevra une chaîne comme "My name is {0} {1} {2}" et doit utiliser GetText ou des ressources ou similaires pour localiser et renvoyer une traduction appropriée pour cela, ou simplement retourner le paramètre d'origine pour ignorer la traduction.

Vous devrez toujours documenter pour vos traducteurs la signification des numéros de paramètres; le texte utilisé dans la chaîne de code d'origine n'existe pas au moment de l'exécution.

Si, par exemple, dans ce cas, GetTranslation a renvoyé "{2}. {0} {2}, {1}. Don't wear it out." (hé, la localisation n'est pas seulement une question de langue!) alors la sortie du programme complet serait:

Bloggs.  John Bloggs, W.  Don't wear it out.

Cela dit, bien que l'utilisation de ce style de traduction soit facile à développer, il est difficile de traduire réellement, car les chaînes sont enterrées dans le code et n'apparaissent qu'au moment de l'exécution. Sauf si vous disposez d'un outil qui peut statiquement explorer votre code et extraire toutes les chaînes traduisibles (sans avoir à frapper ce chemin de code lors de l'exécution), il vaut mieux utiliser des fichiers resx plus traditionnels, car ils sont intrinsèquement vous donner un tableau de texte à traduire.

3
Miral

L'interpolation de chaîne C # 6.0 ne vous aidera pas si la chaîne de format n'est pas dans votre code source C #. Dans ce cas, vous devrez utiliser une autre solution, comme cette bibliothèque .

3
svick

Si nous utilisons l'interpolation, nous pensons en termes de méthodes et non de constantes. Dans ce cas, nous pourrions définir nos traductions comme des méthodes:

public abstract class InterpolatedText
{
    public abstract string GreetingWithName(string firstName, string lastName);
}

public class InterpolatedTextEnglish : InterpolatedText
{
    public override string GreetingWithName(string firstName, string lastName) =>
        $"Hello, my name is {firstName} {lastName}.";
}

Nous pouvons ensuite charger une implémentation de InterpolatedText pour une culture spécifique. Cela permet également d'implémenter le repli, car une implémentation peut hériter d'une autre. Si l'anglais est la langue par défaut et que d'autres implémentations en héritent, il y aura au moins quelque chose à afficher jusqu'à ce qu'une traduction soit fournie.

Cela semble un peu peu orthodoxe, mais offre certains avantages:

Principalement, la chaîne utilisée pour l'interpolation est toujours stockée dans une méthode fortement typée avec des arguments clairement spécifiés.

Compte tenu de ceci: "Hello, my name is {0} {1}" peut-on déterminer que les espaces réservés représentent le prénom et le nom dans cet ordre? Il y aura toujours une méthode qui fait correspondre les valeurs aux espaces réservés, mais il y a moins de confusion lorsque la chaîne interpolée est stockée avec ses arguments.

De même, si nous stockons nos chaînes de traduction à un endroit et les utilisons à un autre, il devient possible de les modifier d'une manière qui casse le code en les utilisant. Nous pouvons ajouter {2} à une chaîne qui sera utilisée ailleurs, et ce code échouera au moment de l'exécution.

L'utilisation de l'interpolation de chaînes est impossible. Si notre chaîne de traduction ne correspond pas aux arguments disponibles, elle ne sera même pas compilée.


Il y a des inconvénients, bien que je vois des difficultés à maintenir une solution.

Le plus grand est la portabilité. Si votre traduction est codée en C # et que vous changez, ce n'est pas la chose la plus simple d'exporter toutes vos traductions.

Cela signifie également que si vous souhaitez confier des traductions à différentes personnes (sauf si vous avez une personne qui parle tout), les traducteurs doivent modifier le code. C'est du code facile, mais du code quand même.

1
Scott Hannen

L'interpolation de chaînes est difficile à combiner avec la localisation car le compilateur préfère la traduire en string.Format(...), qui ne prend pas en charge la localisation. Cependant, il existe une astuce qui permet de combiner localisation et interpolation de chaînes; il est décrit vers la fin de cet article .

L'interpolation de chaîne est normalement traduite en string.Format, Dont le comportement ne peut pas être personnalisé. Cependant, de la même manière que les méthodes lambda deviennent parfois des arbres d'expression, le compilateur passera de string.Format À FormattableStringFactory.Create (Une méthode .NET 4.6) si la méthode cible accepte un System.FormattableString objet.

Le problème est que le compilateur préfère appeler string.Format Si possible, donc s'il y avait une surcharge de Localized() qui acceptait FormattableString, cela ne fonctionnerait pas avec une interpolation de chaîne car le Le compilateur C # l'ignorerait simplement [car il y a une surcharge qui accepte une chaîne ordinaire]. En fait, c'est pire que cela: le compilateur refuse également d'utiliser FormattableString lors de l'appel d'une méthode d'extension.

Cela peut fonctionner si vous utilisez une méthode sans extension. Par exemple:

static class Loca
{
    public static string lize(this FormattableString message)
        { return message.Format.Localized(message.GetArguments()); }
}

Ensuite, vous pouvez l'utiliser comme ceci:

public class Program
{
    public static void Main(string[] args)
    {
        Localize.UseResourceManager(Resources.ResourceManager);

        var name = "Dave";
        Console.WriteLine(Loca.lize($"Hello, {name}"));
    }
}

Il est important de réaliser que le compilateur convertit la chaîne $"..." En une chaîne de format à l'ancienne. Ainsi, dans cet exemple, Loca.lize Reçoit en fait "Hello, {0}" Comme chaîne de format, pas "Hello, {name}".

0
Qwertie