Verwenden Sie ein separates Projekt mit Ressourcen
Ich kann dies aus unserer Erfahrung mit einer aktuellen Lösung mit 12 24 Projekten, die API, MVC, Projektbibliotheken (Kernfunktionen), WPF, UWP und Xamarin umfasst, beurteilen. Es lohnt sich, diesen langen Beitrag zu lesen, da ich denke, dass dies der beste Weg ist, dies zu tun. Mit Hilfe von VS-Tools leicht exportierbar und importierbar, um an Übersetzungsbüros gesendet oder von anderen Personen überprüft zu werden.
EDIT 02/2018: Die Konvertierung in eine .NET Standard-Bibliothek ist immer noch stark und ermöglicht die Verwendung sogar in .NET Framework und NET Core. Ich habe einen zusätzlichen Abschnitt zum Konvertieren in JSON hinzugefügt, damit beispielsweise Angular ihn verwenden kann.
EDIT 2019: Mit Xamarin funktioniert dies weiterhin auf allen Plattformen. ZB empfiehlt Xamarin.Forms, auch Resx-Dateien zu verwenden. (Ich habe noch keine App in Xamarin.Forms entwickelt, aber die Dokumentation, die für den Einstieg viel zu detailliert ist, behandelt sie: Xamarin.Forms-Dokumentation ). Genau wie beim Konvertieren in JSON können wir es auch in eine XML-Datei für Xamarin.Android konvertieren.
EDIT 2019 (2): Beim Upgrade von WPF auf UWP habe ich festgestellt .resw
, dass sie in UWP lieber einen anderen Dateityp verwenden , der inhaltlich identisch ist, aber unterschiedlich verwendet wird. Ich habe einen anderen Weg gefunden, der meiner Meinung nach besser funktioniert als die Standardlösung .
EDIT 2020: Einige Vorschläge für größere (Modulair-) Projekte wurden aktualisiert, für die möglicherweise mehrsprachige Projekte erforderlich sind.
Also, lasst uns loslegen.
Profis
- Fast überall stark getippt.
- In WPF müssen Sie sich nicht damit befassen
ResourceDirectories
.
- Unterstützt für ASP.NET, Klassenbibliotheken, WPF, Xamarin, .NET Core, .NET Standard, soweit ich getestet habe.
- Es werden keine zusätzlichen Bibliotheken von Drittanbietern benötigt.
- Unterstützt Kultur-Fallback: en-US -> en.
- Nicht nur Back-End, funktioniert auch in XAML für WPF und Xamarin.Forms, in .cshtml für MVC.
- Manipulieren Sie die Sprache einfach, indem Sie die ändern
Thread.CurrentThread.CurrentCulture
- Suchmaschinen können in verschiedenen Sprachen crawlen und Benutzer können sprachspezifische URLs senden oder speichern.
Con's
- WPF XAML ist manchmal fehlerhaft, neu hinzugefügte Zeichenfolgen werden nicht direkt angezeigt. Rebuild ist das temporäre Update (vs2015).
- UWP XAML zeigt keine Intellisense-Vorschläge an und zeigt den Text beim Entwerfen nicht an.
- Sag mir.
Installieren
Erstellen Sie ein Sprachprojekt in Ihrer Lösung und geben Sie ihm einen Namen wie MyProject.Language . Fügen Sie einen Ordner mit dem Namen Ressourcen hinzu, und erstellen Sie in diesem Ordner zwei Ressourcendateien (.resx). Eine heißt Resources.resx und eine andere heißt Resources.en.resx (oder .en-GB.resx für bestimmte). In meiner Implementierung habe ich die NL-Sprache (Niederländisch) als Standardsprache, sodass diese in meiner ersten Datei und Englisch in meiner zweiten Datei enthalten ist.
Das Setup sollte folgendermaßen aussehen:
Die Eigenschaften für Resources.resx müssen sein:
Stellen Sie sicher, dass der benutzerdefinierte Tool-Namespace auf Ihren Projekt-Namespace festgelegt ist. Grund dafür ist, dass Sie in WPF nicht auf Resources
XAML verweisen können .
Setzen Sie in der Ressourcendatei den Zugriffsmodifikator auf Öffentlich:
Wenn Sie eine so große Anwendung haben (sagen wir verschiedene Module), können Sie mehrere Projekte wie oben erstellen. In diesem Fall können Sie Ihren Schlüsseln und Ressourcenklassen das jeweilige Modul voranstellen. Verwenden Sie den besten Spracheditor für Visual Studio, um alle Dateien in einer einzigen Übersicht zu kombinieren.
Verwendung in einem anderen Projekt
Verweis auf Ihr Projekt: Klicken Sie mit der rechten Maustaste auf Verweise -> Verweis hinzufügen -> Projekte \ Lösungen.
Verwenden Sie den Namespace in einer Datei: using MyProject.Language;
Verwenden Sie es wie string someText = Resources.orderGeneralError;
folgt im Backend :
Wenn es etwas anderes gibt, das als Ressourcen bezeichnet wird, fügen Sie einfach den gesamten Namespace ein.
Verwendung in MVC
In MVC können Sie die Sprache beliebig einstellen, aber ich habe parametrisierte URLs verwendet, die wie folgt eingerichtet werden können:
RouteConfig.cs
Unter den anderen Zuordnungen
routes.MapRoute(
name: "Locolized",
url: "{lang}/{controller}/{action}/{id}",
constraints: new { lang = @"(\w{2})|(\w{2}-\w{2})" },
defaults: new { controller = "shop", action = "index", id = UrlParameter.Optional }
);
FilterConfig.cs (muss möglicherweise hinzugefügt werden, wenn ja, fügen Sie FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
der Application_start()
Methode in hinzuGlobal.asax
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ErrorHandler.AiHandleErrorAttribute());
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 legt nur die Kulturinformationen fest.
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;
info.DateTimeFormat.DateSeparator = "/";
info.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";
Thread.CurrentThread.CurrentUICulture = info;
Thread.CurrentThread.CurrentCulture = info;
}
catch (Exception)
{
}
}
Verwendung in .cshtml
@using MyProject.Language;
<h3>@Resources.w_home_header</h3>
oder wenn Sie keine Verwendungen definieren möchten, füllen Sie einfach den gesamten Namespace aus ODER Sie können den Namespace unter /Views/web.config definieren:
<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>
Dieses Tutorial zur Quell-Implementierung von mvc: Awesome tutorial blog
Verwendung in Klassenbibliotheken für Modelle
Die Verwendung im Back-End ist dieselbe, jedoch nur ein Beispiel für die Verwendung in Attributen
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; }
}
}
Wenn Sie eine Umgestaltung vorgenommen haben, wird automatisch überprüft, ob der angegebene Ressourcenname vorhanden ist. Wenn Sie die Typensicherheit bevorzugen, können Sie T4-Vorlagen verwenden, um eine Aufzählung zu generieren
Verwendung in WPF.
Natürlich fügen Sie einen Verweis auf Ihren MyProject.Language- Namespace hinzu. Wir wissen, wie man ihn im Back-End verwendet.
Fügen Sie in XAML in der Kopfzeile eines Fensters oder UserControls eine Namespace-Referenz hinzu, die lang
wie folgt heißt :
<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">
Dann in einem Etikett:
<Label x:Name="lblHeader" Content="{x:Static lang:Resources.w_home_header}" TextBlock.FontSize="20" HorizontalAlignment="Center"/>
Da es stark typisiert ist, sind Sie sicher, dass die Ressourcenzeichenfolge vorhanden ist. Möglicherweise müssen Sie das Projekt manchmal während des Setups neu kompilieren. WPF ist manchmal fehlerhaft mit neuen Namespaces.
Eine weitere Sache für WPF, stellen Sie die Sprache innerhalb der App.xaml.cs
. Sie können Ihre eigene Implementierung durchführen (während der Installation auswählen) oder das System entscheiden lassen.
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:
MyProject.Language.Resources.Culture = new System.Globalization.CultureInfo("en-GB");
break;
}
}
}
Verwendung in UWP
In UWP verwendet Microsoft diese Lösung , dh Sie müssen neue Ressourcendateien erstellen. Außerdem können Sie den Text auch nicht wiederverwenden, da Sie x:Uid
das Steuerelement in XAML auf einen Schlüssel in Ihren Ressourcen setzen sollen. Und in Ihren Ressourcen müssen Sie tun Example.Text
, um TextBlock
den Text von a zu füllen . Diese Lösung hat mir überhaupt nicht gefallen, weil ich meine Ressourcendateien wiederverwenden möchte. Schließlich fand ich die folgende Lösung. Ich habe das heute (26.09.2019) herausgefunden, sodass ich möglicherweise mit etwas anderem zurückkomme, wenn sich herausstellt, dass dies nicht wie gewünscht funktioniert.
Fügen Sie dies Ihrem Projekt hinzu:
using Windows.UI.Xaml.Resources;
public class MyXamlResourceLoader : CustomXamlResourceLoader
{
protected override object GetResource(string resourceId, string objectType, string propertyName, string propertyType)
{
return MyProject.Language.Resources.ResourceManager.GetString(resourceId);
}
}
Fügen Sie dies App.xaml.cs
im Konstruktor hinzu:
CustomXamlResourceLoader.Current = new MyXamlResourceLoader();
Wo immer Sie möchten, ändern Sie die Sprache in Ihrer App:
ApplicationLanguages.PrimaryLanguageOverride = "nl";
Frame.Navigate(this.GetType());
Die letzte Zeile wird benötigt, um die Benutzeroberfläche zu aktualisieren. Während ich noch an diesem Projekt arbeite, habe ich festgestellt, dass ich dies zweimal tun muss. Möglicherweise wird beim ersten Start des Benutzers eine Sprachauswahl angezeigt. Da dies jedoch über den Windows Store verteilt wird, entspricht die Sprache normalerweise der Systemsprache.
Dann verwenden Sie in XAML:
<TextBlock Text="{CustomResource ExampleResourceKey}"></TextBlock>
Verwendung in Angular (Konvertierung in JSON)
Heutzutage ist es üblicher, ein Framework wie Angular in Kombination mit Komponenten zu haben, also ohne cshtml. Übersetzungen werden in JSON-Dateien gespeichert. Ich werde nicht darauf eingehen, wie das funktioniert. Ich würde ngx-translate anstelle der eckigen Mehrfachübersetzung nur empfehlen . Wenn Sie also Übersetzungen in eine JSON-Datei konvertieren möchten, ist dies ziemlich einfach. Ich verwende ein T4-Vorlagenskript, das die Ressourcendatei in eine JSON-Datei konvertiert. Ich empfehle, den T4-Editor zu installieren , um die Syntax zu lesen und korrekt zu verwenden, da Sie einige Änderungen vornehmen müssen.
Nur eines ist zu beachten: Es ist nicht möglich, die Daten zu generieren, zu kopieren, zu bereinigen und für eine andere Sprache zu generieren. Sie müssen also den folgenden Code so oft kopieren, wie Sie Sprachen haben, und den Eintrag vor '// Sprache hier auswählen' ändern. Derzeit ist keine Zeit, dies zu beheben, wird aber wahrscheinlich später aktualisiert (falls interessiert).
Pfad: MyProject.Language / T4 / CreateLocalizationEN.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/CreateLocalizationEN.json";
var fileResultPath = Path.Combine(Path.GetDirectoryName(this.Host.ResolvePath("")), "MyProject.Language", fileResultName);
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 };
string[] fileNamesDest = new string[] {fileNameDestEn };
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);
}
#>
Wenn Sie eine Modulair-Anwendung haben und meinem Vorschlag gefolgt sind, mehrsprachige Projekte zu erstellen, müssen Sie für jedes eine T4-Datei erstellen. Stellen Sie sicher, dass die JSON-Dateien logisch definiert sind, es muss nicht sein en.json
, es kann auch sein example-en.json
. Befolgen Sie die Anweisungen hier, um mehrere JSON- Dateien für die Verwendung mit ngx-translate zu kombinieren
Verwendung in Xamarin.Android
Wie oben in den Updates erläutert, verwende ich dieselbe Methode wie bei Angular / JSON. Da Android jedoch XML-Dateien verwendet, habe ich eine T4-Datei geschrieben, die diese XML-Dateien generiert.
Pfad: MyProject.Language / T4 / CreateAppLocalizationEN.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=".xml" #>
<#
var fileName = "../Resources/Resources.en.resx";
var fileResultName = "../T4/CreateAppLocalizationEN.xml";
var fileResultRexPath = Path.Combine(Path.GetDirectoryName(this.Host.ResolvePath("")), "MyProject.Language", fileName);
var fileResultPath = Path.Combine(Path.GetDirectoryName(this.Host.ResolvePath("")), "MyProject.Language", fileResultName);
var fileNameDest = "strings.xml";
var pathBaseDestination = Directory.GetParent(Directory.GetParent(this.Host.ResolvePath("")).ToString()).ToString();
var currentPathDest =pathBaseDestination + "/MyProject.App.AndroidApp/Resources/values-en/" + fileNameDest;
using(var reader = new ResXResourceReader(fileResultRexPath))
{
reader.UseResXDataNodes = true;
#>
<resources>
<#
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", "");
if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("&", "&");
if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("<<", "");
#>
<string name="<#=name#>">"<#=value#>"</string>
<#
}
#>
<string name="WEBSHOP_LASTELEMENT">just ignore this</string>
<#
#>
</resources>
<#
File.Copy(fileResultPath, currentPathDest, true);
}
#>
Android funktioniert mit values-xx
Ordnern, daher ist oben für Englisch im values-en
Ordner. Sie müssen aber auch einen Standard generieren, der in den values
Ordner verschoben wird. Kopieren Sie einfach die obige T4-Vorlage und ändern Sie den Ordner im obigen Code.
Jetzt können Sie eine einzige Ressourcendatei für alle Ihre Projekte verwenden. Dies macht es sehr einfach, alles in ein Ausschlussdokument zu exportieren und es von jemandem übersetzen und erneut importieren zu lassen.
Besonderer Dank geht an diese erstaunliche VS-Erweiterung, die hervorragend mit resx
Dateien funktioniert . Betrachten Sie spenden ihm für seine großartige Arbeit (Ich habe nichts damit zu tun, ich liebe einfach die Erweiterung).