Kaynaklarla ayrı bir proje kullanın
API, MVC, Proje Kitaplıkları (Çekirdek işlevsellikler), WPF, UWP ve Xamarin içeren 12 24 proje ile güncel bir çözüme sahip olduğumdan bunu deneyimlerimden anlayabilirim. Bunu yapmanın en iyi yolu olduğunu düşündüğüm için bu uzun yazıyı okumaya değer. VS araçlarının yardımıyla, çeviri bürolarına gönderilebilir veya başkaları tarafından gözden geçirilebilir.
DÜZENLEME 02/2018: Hala güçlü olmak, onu bir .NET Standard kitaplığına dönüştürmek, onu .NET Framework ve NET Core'da bile kullanmayı mümkün kılıyor. JSON'a dönüştürmek için fazladan bir bölüm ekledim, böylece örneğin açısal bunu kullanabilir.
DÜZENLEME 2019: Xamarin ile birlikte, bu hala tüm platformlarda çalışıyor. Örneğin, Xamarin.Forms, resx dosyalarının da kullanılmasını öneriyor. (Henüz Xamarin.Forms'da bir uygulama geliştirmedim, ancak başlangıç için ayrıntılı olan belgeler, onu kapsar: Xamarin.Forms Belgeleri ). JSON'a dönüştürmek gibi, Xamarin.Android için de .xml dosyasına dönüştürebiliriz.
DÜZENLEME 2019 (2): WPF'den UWP'ye yükseltme yaparken, UWP'de .resw
içerik açısından aynı olan ancak kullanım farklı olan başka bir dosya türünü kullanmayı tercih ettiklerini gördüm . Bunu yapmanın, bence varsayılan çözümden daha iyi çalışan farklı bir yolunu buldum .
EDIT 2020: Birden çok dil projesi gerektirebilecek daha büyük (modulair) projeler için bazı öneriler güncellendi.
Öyleyse başlayalım.
Profesyoneller
- Hemen hemen her yerde güçlü bir şekilde yazılmıştır.
- WPF'de uğraşmak zorunda değilsiniz
ResourceDirectories
.
- ASP.NET, Class Libraries, WPF, Xamarin, .NET Core, .NET Standard için test ettiğim kadarıyla destekleniyor.
- Ekstra üçüncü taraf kitaplıklarına gerek yoktur.
- Kültür geri dönüşünü destekler: en-US -> tr.
- Yalnızca arka uç değil, aynı zamanda WPF ve Xamarin.Forms için XAML'de, MVC için .cshtml'de çalışır.
- Değiştirerek dili kolayca değiştirin.
Thread.CurrentThread.CurrentCulture
- Arama motorları farklı dillerde Tarama yapabilir ve kullanıcı dile özgü url'ler gönderebilir veya kaydedebilir.
Eksileri
- WPF XAML bazen hatalı olabilir, yeni eklenen dizeler doğrudan görünmez. Yeniden oluşturma geçici düzeltme (vs2015).
- UWP XAML, akıllıca önerileri göstermez ve tasarım sırasında metni göstermez.
- Bana söyle.
Kurmak
Çözümünüzde dil projesi oluşturun, ona MyProject.Language gibi bir isim verin . Kaynaklar adında bir klasör ekleyin ve bu klasörde iki Kaynak dosyası (.resx) oluşturun. Biri Resources.resx ve diğeri Resources.en.resx (veya spesifik olarak .en-GB.resx) olarak adlandırıldı. Uygulamamda, varsayılan dil olarak NL (Hollandaca) dilim var, bu yüzden bu ilk dosyamda ve İngilizce ikinci dosyamda yer alıyor.
Kurulum şu şekilde görünmelidir:
Resources.resx'in özellikleri şöyle olmalıdır:
Özel araç ad alanının proje ad alanınız olarak ayarlandığından emin olun. Bunun nedeni, Resources
WPF'de XAML'nin içine başvuramazsınız.
Kaynak dosyasının içinde erişim değiştiriciyi Genel olarak ayarlayın:
Bu kadar büyük bir uygulamanız varsa (diyelim farklı modüller) yukarıdaki gibi birden fazla proje oluşturmayı düşünebilirsiniz. Bu durumda, Anahtarlarınızı ve kaynak sınıflarınızı belirli bir Modül ile önekleyebilirsiniz. Tüm dosyaları tek bir genel bakışta birleştirmek için Visual Studio için var olan en iyi dil düzenleyicisini kullanın .
Başka bir projede kullanmak
Projenize referans: Referanslar -> Referans Ekle -> Prjects \ Solutions'a sağ tıklayın.
Bir dosyada ad alanı kullanın: using MyProject.Language;
Bunu arka uçta olduğu gibi kullanın:
string someText = Resources.orderGeneralError;
Kaynaklar adında başka bir şey varsa, o zaman ad alanının tamamını girin.
MVC'de kullanma
MVC'de dili ayarlamak istediğiniz gibi yapabilirsiniz, ancak aşağıdaki gibi ayarlanabilen parametreli url'ler kullandım:
RouteConfig.cs
Diğer eşlemelerin altında
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 eklemek eğer öyleyse (eklenebilir gerekebilir FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
için Application_start()
de yönteminGlobal.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 sadece Kültür bilgilerini ayarlar.
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)
{
}
}
.Cshtml'de kullanım
@using MyProject.Language;
<h3>@Resources.w_home_header</h3>
veya kullanımları tanımlamak istemiyorsanız, ad alanının tamamını doldurun VEYA ad alanını /Views/web.config altında tanımlayabilirsiniz:
<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>
Bu mvc uygulama kaynağı öğreticisi: Harika öğretici blog
Modeller için sınıf kitaplıklarında kullanma
Arka uç kullanımı aynıdır, ancak özniteliklerde kullanmak için sadece bir örnek
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; }
}
}
Yeniden şekillendirirseniz, verilen kaynak adının var olup olmadığını otomatik olarak kontrol edecektir. Tür güvenliğini tercih ediyorsanız, bir numara oluşturmak için T4 şablonlarını kullanabilirsiniz.
WPF'de kullanma.
Elbette MyProject.Language ad alanınıza bir referans ekleyin , arka uçta nasıl kullanılacağını biliyoruz.
XAML'de, bir Window veya UserControl başlığının içine, lang
şu şekilde adlandırılan bir ad alanı başvurusu ekleyin :
<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">
Ardından, bir etiketin içinde:
<Label x:Name="lblHeader" Content="{x:Static lang:Resources.w_home_header}" TextBlock.FontSize="20" HorizontalAlignment="Center"/>
Kesinlikle yazıldığı için kaynak dizesinin var olduğundan eminsin. Bazen kurulum sırasında projeyi yeniden derlemeniz gerekebilir, WPF bazen yeni ad alanları ile hatalı olabilir.
WPF için bir şey daha, dili App.xaml.cs
. Kendi uygulamanızı yapabilir (kurulum sırasında seçebilirsiniz) veya sistemin karar vermesine izin verebilirsiniz.
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;
}
}
}
UWP'de kullanma
UWP'de Microsoft bu çözümü kullanır , yani yeni kaynak dosyaları oluşturmanız gerekecektir. Ayrıca metni yeniden kullanamazsınız çünkü x:Uid
XAML'deki kontrolünüzü kaynaklarınızdaki bir anahtara ayarlamanızı isterler . Ve kaynaklarınızda Example.Text
bir TextBlock
metnini doldurmanız gerekir . Bu çözümü hiç beğenmedim çünkü kaynak dosyalarımı yeniden kullanmak istiyorum. Sonunda aşağıdaki çözümü buldum. Bunu bugün yeni öğrendim (2019-09-26), bu yüzden istediğim gibi çalışmadığı ortaya çıkarsa başka bir şeyle geri gelebilirim.
Bunu projenize ekleyin:
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);
}
}
Bunu yapıcıya ekleyin App.xaml.cs
:
CustomXamlResourceLoader.Current = new MyXamlResourceLoader();
Uygulamanızda istediğiniz yerde, dili değiştirmek için bunu kullanın:
ApplicationLanguages.PrimaryLanguageOverride = "nl";
Frame.Navigate(this.GetType());
Kullanıcı arayüzünü yenilemek için son satır gereklidir. Hala bu proje üzerinde çalışırken, bunu 2 kez yapmam gerektiğini fark ettim. Kullanıcı ilk başladığında bir dil seçimi yapabilirim. Ancak bu, Windows Mağazası aracılığıyla dağıtılacağından, dil genellikle sistem diline eşittir.
Ardından XAML'de kullanın:
<TextBlock Text="{CustomResource ExampleResourceKey}"></TextBlock>
Angular'da kullanma (JSON'a dönüştür)
Günümüzde Angular gibi bileşenlerle kombinasyon halinde, yani cshtml'siz bir çerçeveye sahip olmak daha yaygın. Çeviriler json dosyalarında saklanır, bunun nasıl çalıştığını anlatmayacağım , sadece açısal çoklu çeviri yerine ngx-translate'i şiddetle tavsiye ederim . Çevirileri bir JSON dosyasına dönüştürmek istiyorsanız, oldukça kolaydır, Resources dosyasını bir json dosyasına dönüştüren bir T4 şablon komut dosyası kullanıyorum. Sözdizimini okumak ve doğru kullanmak için T4 editörü kurmanızı tavsiye ederim çünkü bazı değişiklikler yapmanız gerekiyor.
Unutulmaması gereken tek şey: Veriyi oluşturmak, kopyalamak, verileri temizlemek ve başka bir dil için oluşturmak mümkün değildir. Bu nedenle, aşağıdaki kodu sahip olduğunuz diller kadar kopyalamanız ve '// burada dili seçin' öncesinde girişi değiştirmeniz gerekir. Şu anda bunu düzeltmek için zaman yok, ancak muhtemelen daha sonra güncellenecek (ilgileniyorsanız).
Yol: 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);
}
#>
Modulair uygulamanız varsa ve birden fazla dilde proje oluşturma önerimi takip ettiyseniz, her biri için bir T4 dosyası oluşturmanız gerekecektir. Json dosyalarının mantıksal olarak tanımlandığından emin olun, olması gerekmez en.json
, aynı zamanda olabilir example-en.json
. İle kullanmak için birden fazla json dosyaları birleştirmek için NGX-translate , talimatları takip buraya
Xamarin.Android'de kullanın
Güncellemelerde yukarıda açıklandığı gibi Angular / JSON ile yaptığım yöntemi kullanıyorum. Ancak Android XML dosyalarını kullanıyor, bu yüzden bu XML dosyalarını oluşturan bir T4 dosyası yazdım.
Yol: 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 values-xx
klasörlerle çalışır , bu nedenle yukarıdaki values-en
klasörde İngilizce için geçerlidir . Ancak, values
klasöre giren bir varsayılan oluşturmanız da gerekir . Sadece T4 şablonunun üstüne kopyalayın ve yukarıdaki koddaki klasörü değiştirin.
İşte gidiyorsunuz, artık tüm projeleriniz için tek bir kaynak dosyası kullanabilirsiniz. Bu, her şeyi özel bir belgeye aktarmayı çok kolaylaştırır ve birinin onu çevirip tekrar içeri aktarmasına izin verir.
Dosyalarla harika çalışan bu harika VS eklentisine özel teşekkürler resx
. Müthiş çalışması için ona bağış yapmayı düşünün (bununla hiçbir ilgim yok, sadece uzantıyı seviyorum).