Usa un progetto separato con Risorse
Posso dirlo dalla nostra esperienza, avendo una soluzione attuale con 12 24 progetti che include API, MVC, librerie di progetti (funzionalità di base), WPF, UWP e Xamarin. Vale la pena leggere questo lungo post perché penso che sia il modo migliore per farlo. Con l'aiuto di strumenti VS facilmente esportabili e importabili da inviare ad agenzie di traduzione o revisione da altre persone.
EDIT 02/2018: ancora forte, convertirlo in una libreria .NET Standard rende possibile persino usarlo su .NET Framework e NET Core. Ho aggiunto una sezione extra per convertirlo in JSON in modo che ad esempio angular possa usarlo.
EDIT 2019: andando avanti con Xamarin, funziona ancora su tutte le piattaforme. Ad esempio, Xamarin.Forms consiglia di utilizzare anche i file resx. (Non ho ancora sviluppato un'app in Xamarin.Forms, ma la documentazione, che è molto dettagliata per iniziare, la copre: Xamarin.Forms Documentation ). Proprio come convertirlo in JSON, possiamo anche convertirlo in un file .xml per Xamarin.Android.
EDIT 2019 (2): durante l'aggiornamento a UWP da WPF, ho riscontrato che in UWP preferiscono utilizzare un altro tipo di file .resw
, che è identico in termini di contenuto ma l'utilizzo è diverso. Ho trovato un modo diverso di farlo che, a mio parere, funziona meglio della soluzione predefinita .
EDIT 2020: aggiornati alcuni suggerimenti per progetti più grandi (modulair) che potrebbero richiedere progetti in più lingue.
Quindi, arriviamo a questo.
Professionisti
- Fortemente digitato quasi ovunque.
- In WPF non devi occuparti di
ResourceDirectories
.
- Supportato per ASP.NET, librerie di classi, WPF, Xamarin, .NET Core, .NET Standard per quanto ho testato.
- Non sono necessarie librerie di terze parti aggiuntive.
- Supporta il fallback della cultura: en-US -> en.
- Non solo back-end, funziona anche in XAML per WPF e Xamarin.Forms, in .cshtml per MVC.
- Gestisci facilmente la lingua cambiando il file
Thread.CurrentThread.CurrentCulture
- I motori di ricerca possono eseguire la scansione in diverse lingue e l'utente può inviare o salvare URL specifici della lingua.
Contro
- WPF XAML a volte presenta bug, le stringhe appena aggiunte non vengono visualizzate direttamente. Ricostruire è la correzione temporanea (vs2015).
- UWP XAML non mostra suggerimenti intellisense e non mostra il testo durante la progettazione.
- Dimmi.
Impostare
Crea un progetto linguistico nella tua soluzione, dagli un nome come MyProject.Language . Aggiungi una cartella chiamata Risorse e in quella cartella crea due file Risorse (.resx). Uno chiamato Resources.resx e un altro chiamato Resources.en.resx (o .en-GB.resx per specifico). Nella mia implementazione, ho la lingua NL (olandese) come lingua predefinita, quindi quella va nel mio primo file e l'inglese va nel mio secondo file.
L'installazione dovrebbe essere simile a questa:
Le proprietà per Resources.resx devono essere:
Assicurati che lo spazio dei nomi dello strumento personalizzato sia impostato sullo spazio dei nomi del progetto. Il motivo è che in WPF non è possibile fare riferimento a Resources
XAML all'interno.
E all'interno del file di risorse, imposta il modificatore di accesso su Pubblico:
Se hai un'applicazione così grande (diciamo moduli diversi) puoi considerare di creare più progetti come sopra. In tal caso potresti anteporre alle chiavi e alle classi di risorse il modulo specifico. Usa il miglior editor di lingue disponibile per Visual Studio per combinare tutti i file in un'unica panoramica.
Utilizzo in un altro progetto
Riferimento al progetto: fare clic con il pulsante destro del mouse su Riferimenti -> Aggiungi riferimento -> Prjects \ Solutions.
Usa lo spazio dei nomi in un file: using MyProject.Language;
Usalo in questo modo nel back-end:
string someText = Resources.orderGeneralError;
se c'è qualcos'altro chiamato Risorse, inserisci l'intero spazio dei nomi.
Utilizzo in MVC
In MVC puoi fare come preferisci impostare la lingua, ma ho usato URL parametrizzati, che possono essere impostati in questo modo:
RouteConfig.cs
Sotto le altre mappature
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 (potrebbe essere necessario aggiungere, in tal caso, aggiungere FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
al Application_start()
metodo inGlobal.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 imposta solo le informazioni sulla cultura.
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)
{
}
}
Utilizzo in .cshtml
@using MyProject.Language;
<h3>@Resources.w_home_header</h3>
oppure, se non vuoi definire gli utilizzi, riempi l'intero spazio dei nomi OPPURE puoi definire lo spazio dei nomi in /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>
Questo tutorial sull'implementazione di mvc: blog tutorial impressionante
Utilizzo di librerie di classi per i modelli
L'utilizzo del back-end è lo stesso, ma è solo un esempio per l'utilizzo negli attributi
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; }
}
}
Se hai reshaper, controllerà automaticamente se il nome della risorsa specificato esiste. Se si preferisce l'indipendenza dai tipi, è possibile utilizzare i modelli T4 per generare un'enumerazione
Utilizzo in WPF.
Ovviamente aggiungi un riferimento al tuo MyProject.Language spazio dei nomi , sappiamo come usarlo nel back-end.
In XAML, all'interno dell'intestazione di una finestra o di un controllo utente, aggiungi un riferimento allo spazio dei nomi chiamato in questo lang
modo:
<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">
Quindi, all'interno di un'etichetta:
<Label x:Name="lblHeader" Content="{x:Static lang:Resources.w_home_header}" TextBlock.FontSize="20" HorizontalAlignment="Center"/>
Poiché è fortemente tipizzato, sei sicuro che la stringa della risorsa esista. Potrebbe essere necessario ricompilare il progetto a volte durante l'installazione, WPF a volte è bacato con nuovi spazi dei nomi.
Un'altra cosa per WPF, imposta la lingua all'interno del file App.xaml.cs
. Puoi eseguire la tua implementazione (scegli durante l'installazione) o lasciare che sia il sistema a decidere.
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;
}
}
}
Utilizzo in UWP
In UWP, Microsoft utilizza questa soluzione , il che significa che dovrai creare nuovi file di risorse. Inoltre non puoi riutilizzare il testo perché vogliono che tu imposti il x:Uid
controllo in XAML su una chiave nelle tue risorse. E nelle tue risorse devi fare Example.Text
per riempire un fileTextBlock
testo. Non mi è piaciuta affatto quella soluzione perché voglio riutilizzare i miei file di risorse. Alla fine ho trovato la seguente soluzione. L'ho appena scoperto oggi (2019-09-26), quindi potrei tornare con qualcos'altro se si scopre che non funziona come desiderato.
Aggiungi questo al tuo progetto:
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);
}
}
Aggiungi questo a App.xaml.cs
nel costruttore:
CustomXamlResourceLoader.Current = new MyXamlResourceLoader();
Ovunque tu voglia nella tua app, usa questo per cambiare la lingua:
ApplicationLanguages.PrimaryLanguageOverride = "nl";
Frame.Navigate(this.GetType());
L'ultima riga è necessaria per aggiornare l'interfaccia utente. Mentre sto ancora lavorando a questo progetto ho notato che dovevo farlo 2 volte. Potrei finire con una selezione della lingua la prima volta che l'utente inizia. Ma poiché questo sarà distribuito tramite Windows Store, la lingua è solitamente uguale alla lingua di sistema.
Quindi usa in XAML:
<TextBlock Text="{CustomResource ExampleResourceKey}"></TextBlock>
Usandolo in Angular (converti in JSON)
Oggigiorno è più comune avere un framework come Angular in combinazione con i componenti, quindi senza cshtml. Le traduzioni sono memorizzate in file json, non ho intenzione di spiegare come funziona, consiglio vivamente ngx-translate invece della multi-traduzione angolare. Quindi, se vuoi convertire le traduzioni in un file JSON, è abbastanza semplice, utilizzo uno script modello T4 che converte il file delle risorse in un file json. Consiglio di installare l' editor T4 per leggere la sintassi e usarlo correttamente perché è necessario apportare alcune modifiche.
Solo una cosa da notare: non è possibile generare i dati, copiarli, pulire i dati e generarli per un'altra lingua. Quindi devi copiare il codice di seguito tante volte quante sono le lingue che hai e cambiare la voce prima di "// scegli la lingua qui". Al momento non c'è tempo per risolvere questo problema, ma probabilmente aggiornerà in seguito (se interessato).
Percorso: 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);
}
#>
Se hai un'applicazione modulair e hai seguito il mio suggerimento per creare progetti in più lingue, dovrai creare un file T4 per ciascuno di essi. Assicurati che i file json siano definiti logicamente, non è necessario che lo sia en.json
, può anche essere example-en.json
. Per combinare più file json da utilizzare con ngx-translate , segui le istruzioni qui
Usa in Xamarin.Android
Come spiegato sopra negli aggiornamenti, utilizzo lo stesso metodo che ho fatto con Angular / JSON. Ma Android utilizza file XML, quindi ho scritto un file T4 che genera quei file XML.
Percorso: 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 funziona con le values-xx
cartelle, quindi sopra è per l'inglese nella values-en
cartella. Ma devi anche generare un valore predefinito che va nella values
cartella. Basta copiare sopra il modello T4 e cambiare la cartella nel codice sopra.
Ecco fatto, ora puoi utilizzare un unico file di risorse per tutti i tuoi progetti. Ciò semplifica l'esportazione di tutto in un documento escl e consente a qualcuno di tradurlo e importarlo di nuovo.
Un ringraziamento speciale a questa straordinaria estensione VS che funziona alla grande con i resx
file. Considerare donando a lui per il suo lavoro impressionante (non ho nulla a che fare con questo, ho solo l'amore l'estensione).