web-dev-qa-db-fra.com

Meilleure façon de mettre en œuvre plusieurs langues / mondialisation dans un grand projet .NET

Je vais bientôt travailler sur un grand projet c # et j'aimerais intégrer un support multilingue dès le départ. J'ai joué et je peux le faire fonctionner en utilisant un fichier de ressources distinct pour chaque langue, puis utilisez un gestionnaire de ressources pour charger les chaînes.

Y a-t-il d'autres bonnes approches que je pourrais étudier?

68
tjjjohnson

Utiliser un projet distinct avec des ressources

Je peux le dire par expérience, ayant une solution actuelle avec 12  24 projets qui incluent l'API, MVC, les bibliothèques de projet (fonctionnalités de base), WPF et Xamarin. Il vaut la peine de lire ce long article car je pense que c'est la meilleure façon de le faire. Avec l'aide des outils VS, facilement exportables et importables, envoyés aux agences de traduction ou révisés par d'autres personnes.

EDIT 02/2018: Toujours aussi performant, sa conversion en bibliothèque .NET Standard permet même de l'utiliser sur .NET Framework et NET Core. J'ai ajouté une section supplémentaire pour le convertir en JSON, par exemple angular peut l'utiliser.

EDIT: 2019: À l'avenir avec Xamarin, cela fonctionne toujours sur toutes les plateformes. Par exemple. Xamarin.Forms conseille d'utiliser également les fichiers resx. (Je n'ai pas encore développé d'application dans Xamarin.Forms, mais la documentation, qui est très détaillée pour commencer, le couvre: Xamarin.Forms Documentation ). Tout comme le convertir en JSON, nous pouvons également le convertir en un fichier .xml pour Xamarin.Android (en cours de développement).

Alors, allons-y.

Pro

  • Fortement tapé presque partout.
  • Dans WPF, vous n'avez pas à gérer ResourceDirectories.
  • Pris en charge pour ASP.NET, les bibliothèques de classes, WPF, Xamarin, .NET Core, .NET Standard pour autant que j'ai testé.
  • Aucune bibliothèque tierce supplémentaire n'est requise.
  • Prend en charge le repli de la culture: en-US -> en.
  • Non seulement le back-end, fonctionne également en XAML pour WPF et Xamarin.Forms, en .cshtml pour MVC.
  • Manipulez facilement la langue en modifiant le Thread.CurrentThread.CurrentCulture
  • Les moteurs de recherche peuvent explorer dans différentes langues et l'utilisateur peut envoyer ou enregistrer des URL spécifiques à la langue.

Con

  • WPF XAML est parfois bogué, les chaînes nouvellement ajoutées ne s'affichent pas directement. Reconstruire est le correctif temporaire (vs2015).
  • Dîtes-moi.

Configuration

Créez un projet de langue dans votre solution, donnez-lui un nom comme MyProject.Language. Ajoutez-y un dossier appelé Ressources et, dans ce dossier, créez deux fichiers de ressources (.resx). Un appelé Resources.resx et un autre appelé Resources.en.resx ( ou .en-GB.resx pour spécifique). Dans mon implémentation, j'ai la langue NL (néerlandais) comme langue par défaut, donc cela va dans mon premier fichier, et l'anglais va dans mon deuxième fichier.

Le programme d'installation devrait ressembler à ceci:

language setup project

Les propriétés de Resources.resx doivent être: properties

Assurez-vous que l'espace de noms de l'outil personnalisé est défini sur l'espace de noms de votre projet. La raison en est que dans WPF, vous ne pouvez pas faire référence à Resources dans XAML.

Et dans le fichier de ressources, définissez le modificateur d'accès sur Public:

access modifier

Utilisation dans un autre projet

Référence à votre projet: Faites un clic droit sur Références -> Ajouter une référence -> Projets\Solutions.

Utilisez l'espace de noms dans un fichier: using MyProject.Language;

Utilisez-le comme ceci dans le back-end: string someText = Resources.orderGeneralError; S'il y a quelque chose d'autre appelé Resources, alors mettez-le dans tout l'espace de noms.

Utilisation dans MVC

Dans MVC, vous pouvez faire comme vous voulez définir la langue, mais j'ai utilisé des URL paramétrées, qui peuvent être configurées comme suit:

RouteConfig.cs Sous les autres mappages

routes.MapRoute(
    name: "Locolized",
    url: "{lang}/{controller}/{action}/{id}",
    constraints: new { lang = @"(\w{2})|(\w{2}-\w{2})" },   // en or en-US
    defaults: new { controller = "shop", action = "index", id = UrlParameter.Optional }
);

FilterConfig.cs (peut-être besoin d'être ajouté, le cas échéant, ajoutez FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); à la méthode Application_start() dans Global.asax

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new ErrorHandler.AiHandleErrorAttribute());
        //filters.Add(new HandleErrorAttribute());
        filters.Add(new LocalizationAttribute("nl-NL"), 0);
    }
}

LocalizationAttribute

public class LocalizationAttribute : ActionFilterAttribute
{
    private string _DefaultLanguage = "nl-NL";
    private string[] allowedLanguages = { "nl", "en" };

    public LocalizationAttribute(string defaultLanguage)
    {
        _DefaultLanguage = defaultLanguage;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        string lang = (string) filterContext.RouteData.Values["lang"] ?? _DefaultLanguage;
        LanguageHelper.SetLanguage(lang);
    }
}

LanguageHelper définit simplement les informations sur la culture.

//fixed number and date format for now, this can be improved.
public static void SetLanguage(LanguageEnum language)
{
    string lang = "";
    switch (language)
    {
        case LanguageEnum.NL:
            lang = "nl-NL";
            break;
        case LanguageEnum.EN:
            lang = "en-GB";
            break;
        case LanguageEnum.DE:
            lang = "de-DE";
            break;
    }
    try
    {
        NumberFormatInfo numberInfo = CultureInfo.CreateSpecificCulture("nl-NL").NumberFormat;
        CultureInfo info = new CultureInfo(lang);
        info.NumberFormat = numberInfo;
        //later, we will if-else the language here
        info.DateTimeFormat.DateSeparator = "/";
        info.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";
        Thread.CurrentThread.CurrentUICulture = info;
        Thread.CurrentThread.CurrentCulture = info;
    }
    catch (Exception)
    {

    }
}

Utilisation en .cshtml

@using MyProject.Language;
<h3>@Resources.w_home_header</h3>

ou si vous ne voulez pas définir d'usages, remplissez simplement l'espace de noms complet OR vous pouvez définir l'espace de noms sous /Views/web.config:

<system.web.webPages.razor>
<Host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
  <namespaces>
    ...
    <add namespace="MyProject.Language" />
  </namespaces>
</pages>
</system.web.webPages.razor>

Ce tutoriel source d'implémentation mvc: Awesome tutorial blog

Utilisation dans les bibliothèques de classes pour les modèles

L'utilisation du back-end est la même, mais juste un exemple d'utilisation dans les attributs

using MyProject.Language;
namespace MyProject.Core.Models
{
    public class RegisterViewModel
    {
        [Required(ErrorMessageResourceName = "accountEmailRequired", ErrorMessageResourceType = typeof(Resources))]
        [EmailAddress]
        [Display(Name = "Email")]
        public string Email { get; set; }
    }
}

Si vous avez remodelé, il vérifiera automatiquement si le nom de ressource donné existe. Si vous préférez la sécurité de type, vous pouvez utiliser modèles T4 pour générer une énumération

Utilisation dans WPF.

Bien sûr, ajoutez une référence à votre espace de noms MyProject.Language, nous savons comment l'utiliser en back-end.

En XAML, à l'intérieur de l'en-tête d'une fenêtre ou d'un UserControl, ajoutez une référence d'espace de noms appelée lang comme ceci:

<UserControl x:Class="Babywatcher.App.Windows.Views.LoginView"
         xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.Microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:MyProject.App.Windows.Views"
              xmlns:lang="clr-namespace:MyProject.Language;Assembly=MyProject.Language" <!--this one-->
             mc:Ignorable="d" 
            d:DesignHeight="210" d:DesignWidth="300">

Ensuite, à l'intérieur d'une étiquette:

    <Label x:Name="lblHeader" Content="{x:Static lang:Resources.w_home_header}" TextBlock.FontSize="20" HorizontalAlignment="Center"/>

Puisqu'il est fortement typé, vous êtes sûr que la chaîne de ressource existe. Vous devrez peut-être recompiler le projet parfois pendant l'installation, WPF est parfois bogué avec de nouveaux espaces de noms.

Encore une chose pour WPF, définissez la langue dans le App.xaml.cs. Vous pouvez faire votre propre implémentation (choisir lors de l'installation) ou laisser le système décider.

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        SetLanguageDictionary();
    }

    private void SetLanguageDictionary()
    {
        switch (Thread.CurrentThread.CurrentCulture.ToString())
        {
            case "nl-NL":
                MyProject.Language.Resources.Culture = new System.Globalization.CultureInfo("nl-NL");
                break;
            case "en-GB":
                MyProject.Language.Resources.Culture = new System.Globalization.CultureInfo("en-GB");
                break;
            default://default english because there can be so many different system language, we rather fallback on english in this case.
                MyProject.Language.Resources.Culture = new System.Globalization.CultureInfo("en-GB");
                break;
        }

    }
}

L'utiliser dans Angular (convertir en JSON)

De nos jours, il est plus courant d'avoir un framework comme Angular en combinaison avec des composants, donc sans cshtml. Les traductions sont stockées dans des fichiers json, je ne vais pas expliquer comment cela fonctionne, mais si vous voulez le convertir en fichier JSON, c'est assez simple, j'utilise un script de modèle T4 qui convertit le fichier Resources en un fichier json. Je recommande d'installer éditeur T4 pour lire la syntaxe et l'utiliser correctement parce que vous devez faire quelques modifications.

Une seule chose à noter: il n'est pas possible de générer les données, de les copier, de nettoyer les données et de les générer pour une autre langue. Vous devez donc copier le code ci-dessous autant de fois que vous avez de langues et modifier l'entrée avant "// choisissez la langue ici". Actuellement, pas le temps de résoudre ce problème, mais sera probablement mis à jour plus tard (si vous êtes intéressé).

Chemin: MyProject.Language/T4/CreateWebshopLocalizationEN.tt

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ Assembly name="System.Core" #>
<#@ Assembly name="System.Windows.Forms" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Resources" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.ComponentModel.Design" #>
<#@ output extension=".json" #>
<#


var fileNameNl = "../Resources/Resources.resx";
var fileNameEn = "../Resources/Resources.en.resx";
var fileNameDe = "../Resources/Resources.de.resx";
var fileNameTr = "../Resources/Resources.tr.resx";

var fileResultName = "../T4/CreateWebshopLocalizationEN.json";//choose language here
var fileResultPath = Path.Combine(Path.GetDirectoryName(this.Host.ResolvePath("")), "MyProject.Language", fileResultName);
//var fileDestinationPath = "../../MyProject.Web/ClientApp/app/i18n/";

var fileNameDestNl = "nl.json";
var fileNameDestEn = "en.json";
var fileNameDestDe = "de.json";
var fileNameDestTr = "tr.json";

var pathBaseDestination = Directory.GetParent(Directory.GetParent(this.Host.ResolvePath("")).ToString()).ToString();

string[] fileNamesResx = new string[] {fileNameEn }; //choose language here
string[] fileNamesDest = new string[] {fileNameDestEn }; //choose language here

for(int x = 0; x < fileNamesResx.Length; x++)
{
    var currentFileNameResx = fileNamesResx[x];
    var currentFileNameDest = fileNamesDest[x];
    var currentPathResx = Path.Combine(Path.GetDirectoryName(this.Host.ResolvePath("")), "MyProject.Language", currentFileNameResx);
    var currentPathDest =pathBaseDestination + "/MyProject.Web/ClientApp/app/i18n/" + currentFileNameDest;
    using(var reader = new ResXResourceReader(currentPathResx))
    {
        reader.UseResXDataNodes = true;
#>
        {
<#
            foreach(DictionaryEntry entry in reader)
            {
                var name = entry.Key;
                var node = (ResXDataNode)entry.Value;
                var value = node.GetValue((ITypeResolutionService) null); 
                 if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("\n", "");
                 if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("\r", "");
#>
            "<#=name#>": "<#=value#>",
<#


            }
#>
        "WEBSHOP_LASTELEMENT": "just ignore this, for testing purpose"
        }
<#
    }
    File.Copy(fileResultPath, currentPathDest, true);
}


#>

Voilà, vous pouvez désormais utiliser un seul fichier de ressources pour tous vos projets. Il est donc très facile d'exporter tout vers un document excl et de laisser quelqu'un le traduire et l'importer à nouveau.

50
CularBytes

J'ai vu des projets mis en œuvre en utilisant un certain nombre d'approches différentes, chacune ayant ses avantages et ses inconvénients.

  • On l'a fait dans le fichier de configuration (pas mon préféré)
  • On l'a fait en utilisant une base de données - cela a très bien fonctionné, mais cela a été pénible car vous savez quoi maintenir.
  • L'un a utilisé des fichiers de ressources comme vous le suggérez et je dois dire que c'était mon approche préférée.
  • Le plus simple l'a fait en utilisant un fichier include plein de chaînes - moche.

Je dirais que la méthode de ressources que vous avez choisie est très logique. Il serait également intéressant de voir les réponses des autres, car je me demande souvent s'il existe une meilleure façon de faire des choses comme celle-ci. J'ai vu de nombreuses ressources qui pointent toutes vers la méthode d'utilisation des ressources, y compris ne ici sur SO .

20
BenAlabaster

Je ne pense pas qu'il existe une "meilleure façon". Cela dépendra vraiment des technologies et du type d'application que vous construisez.

Les applications Web peuvent stocker les informations dans la base de données comme l'ont suggéré d'autres affiches, mais je recommande d'utiliser des fichiers de ressources séparés. C'est-à-dire des fichiers de ressources séparés de votre source . Les fichiers de ressources séparés réduisent les conflits pour les mêmes fichiers et au fur et à mesure que votre projet se développe, la localisation se fera séparément de la logique métier. (Programmeurs et traducteurs).

Les gourous de Microsoft WinForm et WPF recommandent d'utiliser des assemblages de ressources séparés personnalisés pour chaque paramètre régional.

La capacité de WPF à dimensionner les éléments de l'interface utilisateur au contenu réduit le travail de mise en page requis, par exemple: (les mots japonais sont beaucoup plus courts que l'anglais).

Si vous envisagez WPF: je suggère de lire cet article msdn Pour être honnête, j'ai trouvé les outils de localisation WPF: msbuild, locbaml, (et peut-être une feuille de calcul Excel) fastidieux à utiliser, mais cela fonctionne.

Quelque chose seulement légèrement lié: Un problème commun auquel je suis confronté est l'intégration de systèmes hérités qui envoient des messages d'erreur (généralement en anglais), pas des codes d'erreur. Cela oblige soit à modifier les systèmes hérités, soit à mapper les chaînes d'arrière-plan à mes propres codes d'erreur, puis à des chaînes localisées ... yech. Les codes d'erreur sont des localisations ami

5
MW_dev

+1 Base de données

Les formulaires de votre application peuvent même se traduire à nouveau à la volée si des corrections sont apportées à la base de données.

Nous avons utilisé un système dans lequel tous les contrôles étaient mappés dans un fichier XML (un par formulaire) aux ID de ressources linguistiques, mais tous les ID se trouvaient dans la base de données.

Fondamentalement, au lieu que chaque contrôle détienne l'ID (implémentant une interface ou utilisant la propriété de balise dans VB6), nous avons utilisé le fait que dans .NET, l'arborescence de contrôle était facilement détectable par réflexion. Un processus lorsque le formulaire chargé créerait le fichier XML s'il était manquant. Le fichier XML mapperait les contrôles à leurs ID de ressource, il fallait donc simplement les remplir et les mapper à la base de données. Cela signifiait qu'il n'était pas nécessaire de modifier le binaire compilé si quelque chose n'était pas balisé, ou s'il devait être divisé en un autre ID (certains mots en anglais qui pourraient être utilisés à la fois comme noms et verbes pourraient devoir être traduits en deux mots différents dans le dictionnaire et ne pas être réutilisé, mais vous risquez de ne pas le découvrir lors de l'attribution initiale des ID). Mais le fait est que l'ensemble du processus de traduction devient complètement indépendant de votre binaire (chaque formulaire doit hériter d'un formulaire de base qui sait se traduire lui-même et tous ses contrôles).

Les seules où l'application s'implique davantage sont lorsqu'une phase avec des points d'insertion est utilisée.

Le logiciel de traduction de base de données était votre écran de maintenance CRUD de base avec diverses options de flux de travail pour faciliter le traitement des traductions manquantes, etc.

4
Cade Roux

Vous pouvez utiliser des outils commerciaux comme Sisulizer . Il créera un assemblage satellite pour chaque langue. La seule chose à laquelle vous devez faire attention est de ne pas masquer les noms de classe de formulaire (si vous utilisez l'obscurcisseur).

2
Davorin

J'ai cherché et j'ai trouvé ceci:

Si vous utilisez WPF ou Silverlight, votre approche pourrait être utilisée WPF LocalizationExtension pour de nombreuses raisons.

IT Open Source C'est GRATUIT (et restera gratuit) est dans un véritable état de stabel

Dans une application Windows, vous pouvez faire quelque chose comme ceci:

public partial class App : Application  
{  
     public App()  
     {             
     }  

     protected override void OnStartup(StartupEventArgs e)  
     {  
         Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("de-DE"); ;  
         Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("de-DE"); ;  

          FrameworkElement.LanguageProperty.OverrideMetadata(  
              typeof(FrameworkElement),  
              new FrameworkPropertyMetadata(  
                  XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));  
          base.OnStartup(e);  
    }  
} 

Et je pense que sur une page Wep, l'approche pourrait être la même.

Bonne chance!

2
JxXx

J'irais avec les multiples fichiers de ressources. Cela ne devrait pas être si difficile à configurer. En fait, j'ai récemment répondu à une question similaire sur la définition d'un fichier de ressources basé sur une langue globale conjointement avec des fichiers de ressources de langage de formulaire.

Localisation dans Visual Studio 2008

Je considérerais que la meilleure approche au moins pour le développement de WinForm.

2
KMessenger

Nous utilisons un fournisseur personnalisé pour la prise en charge multilingue et mettons tous les textes dans une table de base de données. Cela fonctionne bien, sauf que nous rencontrons parfois des problèmes de mise en cache lors de la mise à jour des textes dans la base de données sans mettre à jour l'application Web.

0
Cossintan

La plupart des projets open source utilisent GetText à cet effet. Je ne sais pas comment et si cela a déjà été utilisé sur un projet .Net auparavant.

0
Vasil

Les fichiers de ressources standard sont plus faciles. Cependant, si vous disposez de données dépendantes de la langue telles que des tables de recherche, vous devrez gérer deux ensembles de ressources.

Je ne l'ai pas fait, mais dans mon prochain projet, j'implémenterais un fournisseur de ressources de base de données. J'ai trouvé comment le faire sur MSDN:

http://msdn.Microsoft.com/en-us/library/aa905797.aspx

J'ai également trouvé cette implémentation:

Fournisseur DBResource

0
Ben Dempsey