La meilleure façon de mettre en œuvre le multilinguisme/la mondialisation dans large.NET projet

je vais bientôt travailler sur un grand projet c# et je voudrais construire dans le soutien multi-langues dès le début. J'ai eu un jeu autour et peut le faire fonctionner en utilisant un fichier ressource séparé pour chaque langue, puis utiliser un gestionnaire de ressources pour charger les chaînes.

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

54
demandé sur Rich Seller 2008-12-17 04:23:56

10 réponses

utiliser un projet distinct avec des ressources

je peux le dire à partir de notre expérience, ayant une solution actuelle avec 12 projets qui comprend API, MVC, bibliothèques de projet (fonctionnalités de base) et WPF. Je n'ai pas encore testé cela avec des bibliothèques portables parce que je n'ai pas créé une bibliothèque Portable pour la bibliothèque de langue .

EDIT 02/2018: va toujours fort, en le convertissant en un La bibliothèque Standard de .NET permet même de l'utiliser à travers .NET Framework et NET Core. J'ai ajouté une section supplémentaire pour la convertir en JSON afin que par exemple angular puisse l'utiliser.

alors, allons-y.

Pro

  • fortement tapé presque partout.
  • dans WPF vous n'avez pas à traiter avec ResourceDirectories .
  • supporté pour ASP.NET, Class Libraries, WPF, .net Core autant que je l'ai testé.
  • pas besoin de bibliothèques tierces supplémentaires.
  • supporte le repli de la culture: en-US -> en.
  • non seulement back-end, fonctionne aussi dans XAML pour WPF, in .cshtml pour MVC.
  • manipule facilement le langage en changeant le Thread.CurrentThread.CurrentCulture
  • les moteurs de recherche peuvent ramper dans différentes langues et l'utilisateur peut envoyer ou enregistrer la langue spécifique URL.

Con

  • WPF XAML est parfois buggy, nouvellement ajouté des chaînes n'apparaissent pas directement. Reconstruire est le correctif temporaire (vs2015).
  • dites-moi.

Setup

créer un projet de langue dans votre solution, lui donner un nom comme MyProject.Langue . Ajouter un dossier pour cela appelé ressources, et dans ce dossier, créer deux fichiers de ressources (.resx). Un appelé ressources.resx et un autre appelé ressources.fr.resx (ou .en-GB.resx pour certains). Dans ma mise en œuvre, 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.

Setup devrait ressembler à ceci:

language setup project

Les propriétés de Ressources.resx doit être: properties

assurez-vous que l'outil personnalisé namespace est défini à votre projet namespace. 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 à Public:

access modifier

utilisant dans un autre projet

référence à votre projet: clic droit sur les références -> ajouter une référence -> Prjects\Solutions.

utiliser namespace dans un fichier: using MyProject.Language;

utilisez - le comme ça à l'arrière: string someText = Resources.orderGeneralError; S'il y a quelque chose d'autre appelé ressources, alors mettez juste dans l'espace de noms entier.

utilisant en MVC

dans MVC vous pouvez faire comme vous voulez pour définir la langue, mais j'ai utilisé des url paramétrées, qui peuvent être configurées comme ceci:

RouteConfig.cs Au-dessous des autres mappings

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 (pourrait devoir être ajouté, si oui, ajouter 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 juste définit la Culture de l'info.

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

    }
}

Usage dans .cshtml

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

ou si vous ne voulez pas définir les utilisations, remplissez simplement l'espace de noms entier ou vous pouvez définir l'espace de noms sous /vues/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>

Cette mvc mise en œuvre de la source tutoriel: impressionnant tutoriel blog

utilisation dans les bibliothèques de classe pour les modèles

Back-end de l'aide est le même, mais juste un exemple pour l'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 reshaper, il vérifiera automatiquement si le nom de la ressource existe. Si vous préférez la sécurité de type, vous pouvez utiliser gabarits T4 pour générer un enum

Utilisation de WPF.

bien sûr, ajoutez une référence à votre projet .Langue namespace, nous savons comment l'utiliser dans back-end.

dans XAML, dans l'en-tête D'une fenêtre ou UserControl, ajouter une référence d'espace de noms appelée lang comme suit:

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

puis, à 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 tapé, vous êtes sûr que la chaîne de ressources exister. Vous pourriez avoir besoin de recompiler le projet parfois lors de la configuration, WPF est parfois buggé avec de nouveaux espaces de noms.

encore une chose pour WPF, mettez le langage à l'intérieur du App.xaml.cs . Vous pouvez faire votre propre implémentation (choisissez pendant 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 en angle (convertir en JSON)

de nos jours, il est plus commun d'avoir un cadre comme Angulaire en combinaison avec les composants, donc sans cshtml. Les traductions sont stockées dans des fichiers json, Je ne vais pas couvrir comment cela fonctionne, mais si vous voulez convertir cela en un fichier JSON, c'est assez facile, j'utilise un script de template 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 avez besoin de faire quelques modifications.

une seule chose à noter: Il n'est pas possible de générez les données, copiez-les, nettoyez les données et générez-les 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 '//choose language here'. Actuellement pas le temps de corriger cela, mais mettra probablement à jour plus tard (si 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 maintenant utiliser un seul fichier de ressources pour tous vos projets. Il est donc très facile de tout exporter vers un document excl et de laisser quelqu'un le traduire et l'importer à nouveau.

21
répondu CularBytes 2018-03-11 13:28:48

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

  • On ne dans le fichier de config (pas mon préféré),
  • L'un l'a fait en utilisant une base de données - cela a fonctionné assez bien, mais était une douleur dans le vous savez quoi maintenir.
  • L'un d'eux a utilisé des fichiers ressources comme vous le suggérez et je dois dire que c'était mon approche préférée.
  • le plus basique, en fait inclure un fichier complet de chaînes de moche.

je dirais que la méthode des ressources que vous avez choisie a beaucoup de sens. Il serait intéressant de voir les réponses des autres, car je me demande souvent s'il y a une meilleure façon de faire ce genre de choses. J'ai vu de nombreuses ressources qui pointent tous vers la méthode de l'utilisation des ressources, y compris un ici sur SO .

19
répondu BenAlabaster 2017-05-23 12:34:18

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

Webapps peut stocker l'information dans la base de données comme d'autres affiches l'ont suggéré, mais je recommande d'utiliser des fichiers de ressources séparées. C'est-à-dire les fichiers de ressources se séparent de votre source . Séparer les fichiers de ressources réduit la discorde pour les mêmes fichiers et que votre projet se développe vous pouvez trouver la localisation sera fait separemment 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 et personnalisés pour chaque localité.

WPF capacité de la taille des éléments de l'INTERFACE utilisateur de contenu abaisse la mise en page de travail nécessaire, par exemple: (les mots japonais sont beaucoup plus courtes que l'anglais).

si vous envisagez WPF: je vous suggère la lecture de cet article msdn Pour être honnête, j'ai trouvé l' Outils de localisation WPF: msbuild, locbaml, (et peut-être une feuille de calcul excel) fastidieux à utiliser, mais il ne fonctionne.

quelque chose qui n'est que légèrement lié: un problème courant auquel je fais face est l'intégration de systèmes existants qui envoient des messages d'erreur (habituellement en anglais), pas des codes d'erreur. Cela oblige soit à modifier des systèmes existants, soit à mapper des 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
répondu MW_dev 2008-12-17 04:07:34

+1 Base de données

Les formulaires

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

nous avons utilisé un système où tous les contrôles étaient mis en correspondance dans un fichier XML (un par formulaire) avec les identificateurs de ressources linguistiques, mais tous les identificateurs étaient dans la base de données.

fondamentalement, au lieu d'avoir chaque contrôle tenir L'ID (implémentation d'une interface, ou en utilisant la propriété tag dans VB6), nous avons utilisé le fait que dans .NET, l'arbre témoin était facilement identifiable par la réflexion. Un processus dans lequel le formulaire chargé compilerait le fichier XML s'il manquait. Le fichier XML établirait une correspondance entre les contrôles et leurs identificateurs de ressources, de sorte qu'il suffisait de le remplir et d'établir une correspondance avec la base de données. Cela signifiait qu'il n'y avait pas besoin de changer le binaire compilé si quelque chose n'était pas étiqueté, ou s'il devait être divisé en un autre ID (certains mots en anglais qui pourrait être utilisé à la fois les noms et les verbes pourraient avoir besoin de traduire à deux mots différents dans le dictionnaire et ne pas être réutilisé, mais vous pourriez ne pas découvrir cela lors de l'attribution initiale de L'IDs). Mais le fait est que tout le processus de traduction devient complètement indépendant de votre binaire (chaque forme doit hériter d'une forme de base qui sait se traduire et tous ses contrôles).

les seuls où l'application devient plus impliqué est quand une phase avec des points d'insertion est utilisé.

traduction de la base de données le logiciel était votre écran de maintenance CRUD de base avec diverses options de flux de travail pour faciliter le passage à travers les traductions manquantes,etc.

4
répondu Cade Roux 2008-12-17 02:47:27

j'utiliserais les multiples fichiers de ressources. Il ne devrait pas être difficile à configurer. En fait, j'ai récemment répondu à une question similaire sur la mise en place d'un global language based resource files en conjonction avec form language resource files.

localisation dans Visual Studio 2008

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

2
répondu KMessenger 2017-05-23 12:26:04

vous pouvez utiliser des outils commerciaux comme Sisulizer . Il créera un assemblage par satellite pour chaque langue. La seule chose que vous devez faire attention est de ne pas obscurcir les noms de classe de forme (si vous utilisez obfuscator).

2
répondu Davorin 2008-12-17 08:59:01

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

Si votre utilisation de WPF ou Silverlight votre approche pourrait être d'utiliser WPF LocalizationExtension pour de nombreuses raisons.

Son Open Source Son libre (et restera libre) est dans un vrai stabel état

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'aproach pourrait être le même.

Bonne Chance!

2
répondu JxXx 2014-01-07 10:30:46

la plupart des projets opensource utilisent GetText à cette fin. Je ne sais pas s'il a déjà été utilisé sur un projet.Net.

0
répondu Vasil 2008-12-17 03:13:16

nous utilisons un fournisseur personnalisé pour le soutien de plusieurs langues 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 de textes dans la base de données sans mettre à jour l'application web.

0
répondu Cossintan 2008-12-17 08:53:39
Les fichiers de ressources Standard

sont plus faciles. Cependant, si vous avez des données dépendantes de la langue comme les tables de recherche, vous devrez gérer deux ensembles de ressources.

Je ne l'ai pas fait, mais dans mon prochain projet, je mettrais en œuvre 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 aussi trouvé cette mise en œuvre:

DBResource Provider

0
répondu Ben Dempsey 2010-02-13 18:46:10