ใช้โปรเจ็กต์แยกกับทรัพยากร
ฉันสามารถบอกสิ่งนี้ได้จากประสบการณ์ภายนอกโดยมีโซลูชันปัจจุบันกับ12 24โครงการซึ่งรวมถึง API, MVC, ไลบรารีโครงการ (ฟังก์ชันหลัก), WPF, UWP และ Xamarin ควรอ่านโพสต์ยาว ๆ นี้เพราะคิดว่าเป็นวิธีที่ดีที่สุด ด้วยความช่วยเหลือของเครื่องมือ VS สามารถส่งออกและนำเข้าได้อย่างง่ายดายเพื่อส่งไปยังหน่วยงานแปลหรือตรวจสอบโดยบุคคลอื่น
แก้ไข 02/2018:ยังคงแข็งแกร่งการแปลงเป็นไลบรารี. NET Standard ทำให้สามารถใช้งานได้กับ. NET Framework และ NET Core ฉันเพิ่มส่วนพิเศษสำหรับการแปลงเป็น JSON เพื่อให้ตัวอย่างเช่นเชิงมุมสามารถใช้งานได้
แก้ไข 2019:ก้าวไปข้างหน้ากับ Xamarin สิ่งนี้ยังคงใช้ได้กับทุกแพลตฟอร์ม เช่น Xamarin.Forms แนะนำให้ใช้ไฟล์ resx ด้วย (ฉันยังไม่ได้พัฒนาแอปใน Xamarin.Forms แต่เอกสารนี้เป็นวิธีที่จะอธิบายรายละเอียดในการเริ่มต้นใช้งานครอบคลุม: เอกสาร Xamarin.Forms ) เช่นเดียวกับการแปลงเป็น JSON เราสามารถแปลงเป็นไฟล์. xml สำหรับ Xamarin.Android
แก้ไข 2019 (2):ในขณะที่อัปเกรดเป็น UWP จาก WPF ฉันพบว่าใน UWP พวกเขาชอบใช้ประเภทไฟล์อื่น.resw
ซึ่งในแง่ของเนื้อหาเหมือนกัน แต่การใช้งานแตกต่างกัน ผมพบว่าวิธีที่แตกต่างกันของการทำเช่นนี้ซึ่งในความคิดของฉันทำงานได้ดีขึ้นแล้วการแก้ปัญหาเริ่มต้น
แก้ไข 2020:อัปเดตคำแนะนำสำหรับโปรเจ็กต์ขนาดใหญ่ (โมดูล) ที่อาจต้องใช้โปรเจ็กต์หลายภาษา
ไปดูกันเลย
ข้อดี
- พิมพ์ผิดเกือบทุกที่
- ใน WPF
ResourceDirectories
คุณไม่ได้มีการจัดการกับ
- รองรับ ASP.NET, Class Libraries, WPF, Xamarin, .NET Core, .NET Standard เท่าที่ฉันได้ทดสอบ
- ไม่จำเป็นต้องมีไลบรารีของบุคคลที่สามเพิ่มเติม
- รองรับวัฒนธรรมสำรอง: en-US -> th
- ไม่เพียง แต่แบ็คเอนด์เท่านั้น แต่ยังทำงานใน XAML สำหรับ WPF และ Xamarin.Forms ในรูปแบบ. cshtml สำหรับ MVC
- ปรับแต่งภาษาได้อย่างง่ายดายโดยการเปลี่ยน
Thread.CurrentThread.CurrentCulture
- เครื่องมือค้นหาสามารถรวบรวมข้อมูลในภาษาต่างๆและผู้ใช้สามารถส่งหรือบันทึก URL เฉพาะภาษาได้
Con's
- WPF XAML บางครั้งมีปัญหาสตริงที่เพิ่มใหม่จะไม่แสดงโดยตรง สร้างใหม่คือการแก้ไขอุณหภูมิ (vs2015)
- UWP XAML ไม่แสดงคำแนะนำ Intellisense และไม่แสดงข้อความขณะออกแบบ
- บอกฉัน.
ติดตั้ง
สร้างโครงการภาษาในการแก้ปัญหาของคุณให้มันชื่อเหมือนMyProject.Language เพิ่มโฟลเดอร์ชื่อ Resources และในโฟลเดอร์นั้นสร้างไฟล์ Resources สองไฟล์ (.resx) หนึ่งเรียกว่าResources.resxและอีกชื่อหนึ่งเรียกว่าResources.en.resx (หรือ. en-GB.resx สำหรับเฉพาะ) ในการใช้งานของฉันฉันมีภาษา NL (ดัตช์) เป็นภาษาเริ่มต้นดังนั้นสิ่งนั้นจะอยู่ในไฟล์แรกของฉันและภาษาอังกฤษจะอยู่ในไฟล์ที่สองของฉัน
การตั้งค่าควรมีลักษณะดังนี้:
คุณสมบัติสำหรับ Resources.resx ต้องเป็น:
ตรวจสอบให้แน่ใจว่าเนมสเปซเครื่องมือที่กำหนดเองถูกตั้งค่าเป็นเนมสเปซโปรเจ็กต์ของคุณ เหตุผลคือใน WPF คุณไม่สามารถอ้างอิงถึงResources
ภายใน XAML
และภายในไฟล์ทรัพยากรตั้งค่าตัวปรับการเข้าถึงเป็นสาธารณะ:
หากคุณมีแอปพลิเคชันขนาดใหญ่เช่นนี้ (สมมติว่าเป็นโมดูลที่แตกต่างกัน) คุณสามารถพิจารณาสร้างโปรเจ็กต์หลาย ๆ โปรเจ็กต์ดังกล่าวข้างต้น ในกรณีนี้คุณสามารถนำหน้าคีย์และคลาสทรัพยากรของคุณด้วยโมดูลเฉพาะได้ ใช้โปรแกรมแก้ไขภาษาที่ดีที่สุดสำหรับ Visual Studio เพื่อรวมไฟล์ทั้งหมดไว้ในภาพรวมเดียว
ใช้ในโครงการอื่น
การอ้างอิงโครงการของคุณ: คลิกขวาที่ References -> Add Reference -> Prjects \ Solutions
ใช้เนมสเปซในไฟล์: using MyProject.Language;
ใช้แบบนี้ในส่วนหลัง:
string someText = Resources.orderGeneralError;
หากมีสิ่งอื่นที่เรียกว่าทรัพยากรให้ใส่ในเนมสเปซทั้งหมด
ใช้ใน MVC
ใน MVC คุณสามารถทำได้ตามที่คุณต้องการตั้งค่าภาษา แต่ฉันใช้ url ที่กำหนดพารามิเตอร์ซึ่งสามารถตั้งค่าได้ดังนี้:
RouteConfig.cs
ด้านล่างการแมปอื่น ๆ
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 (อาจจะต้องมีการเพิ่มถ้าให้เพิ่มFilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
ไปที่Application_start()
วิธีการในGlobal.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เพียงแค่ตั้งค่าข้อมูลวัฒนธรรม
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
@using MyProject.Language;
<h3>@Resources.w_home_header</h3>
หรือหากคุณไม่ต้องการกำหนดการใช้งานให้กรอกเนมสเปซทั้งหมดหรือคุณสามารถกำหนดเนมสเปซภายใต้ /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>
บทช่วยสอนการใช้งาน mvc นี้: บล็อกการสอนที่ยอดเยี่ยม
ใช้ในไลบรารีคลาสสำหรับโมเดล
การใช้แบ็คเอนด์จะเหมือนกัน แต่เป็นเพียงตัวอย่างสำหรับใช้ในแอตทริบิวต์
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; }
}
}
หากคุณมี reshaper ระบบจะตรวจสอบโดยอัตโนมัติว่ามีชื่อทรัพยากรที่ระบุหรือไม่ หากคุณต้องการความปลอดภัยในการพิมพ์คุณสามารถใช้เทมเพลต T4 เพื่อสร้าง enum
ใช้ใน WPF
แน่นอนเพิ่มการอ้างอิงไปยังMyProjectของคุณเนมสเปซภาษาเรารู้วิธีใช้ในส่วนหลัง
ใน XAML ภายในส่วนหัวของหน้าต่างหรือ UserControl ให้เพิ่มการอ้างอิงเนมสเปซที่เรียกว่าlang
:
<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">
จากนั้นภายในป้ายกำกับ:
<Label x:Name="lblHeader" Content="{x:Static lang:Resources.w_home_header}" TextBlock.FontSize="20" HorizontalAlignment="Center"/>
เนื่องจากมีการพิมพ์อย่างมากคุณจึงแน่ใจว่ามีสตริงทรัพยากรอยู่ คุณอาจต้องคอมไพล์โครงการใหม่ในบางครั้งในระหว่างการตั้งค่าบางครั้ง WPF อาจมีปัญหากับเนมสเปซใหม่
อีกอย่างสำหรับ WPF ให้ตั้งค่าภาษาภายในไฟล์App.xaml.cs
. คุณสามารถดำเนินการเองได้ (เลือกระหว่างการติดตั้ง) หรือปล่อยให้ระบบตัดสินใจ
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
ใน UWP Microsoft ใช้โซลูชันนี้ซึ่งหมายความว่าคุณจะต้องสร้างไฟล์ทรัพยากรใหม่ นอกจากนี้คุณไม่สามารถใช้ข้อความซ้ำได้เนื่องจากต้องการให้คุณตั้งค่าการx:Uid
ควบคุมของคุณใน XAML เป็นคีย์ในทรัพยากรของคุณ และในทรัพยากรของคุณคุณต้องทำExample.Text
เพื่อเติมTextBlock
ข้อความ ฉันไม่ชอบโซลูชันนั้นเลยเพราะฉันต้องการใช้ไฟล์ทรัพยากรของฉันซ้ำ ในที่สุดฉันก็คิดวิธีแก้ปัญหาต่อไปนี้ ฉันเพิ่งพบสิ่งนี้ในวันนี้ (2019-09-26) ดังนั้นฉันอาจจะกลับมาพร้อมอย่างอื่นหากปรากฎว่ามันไม่ได้ผลตามที่ต้องการ
เพิ่มสิ่งนี้ในโครงการของคุณ:
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);
}
}
เพิ่มสิ่งนี้App.xaml.cs
ในตัวสร้าง:
CustomXamlResourceLoader.Current = new MyXamlResourceLoader();
ทุกที่ที่คุณต้องการในแอปของคุณใช้สิ่งนี้เพื่อเปลี่ยนภาษา:
ApplicationLanguages.PrimaryLanguageOverride = "nl";
Frame.Navigate(this.GetType());
ต้องใช้บรรทัดสุดท้ายเพื่อรีเฟรช UI ในขณะที่ฉันยังทำงานในโครงการนี้ฉันสังเกตเห็นว่าฉันต้องทำ 2 ครั้งนี้ ฉันอาจลงเอยด้วยการเลือกภาษาในครั้งแรกที่ผู้ใช้เริ่มต้น แต่เนื่องจากสิ่งนี้จะเผยแพร่ผ่าน Windows Store ภาษามักจะเท่ากับภาษาของระบบ
จากนั้นใช้ใน XAML:
<TextBlock Text="{CustomResource ExampleResourceKey}"></TextBlock>
ใช้ใน Angular (แปลงเป็น JSON)
ปัจจุบันเป็นเรื่องปกติมากขึ้นที่จะมีเฟรมเวิร์กเช่น Angular ร่วมกับส่วนประกอบดังนั้นหากไม่มี cshtml การแปลจะถูกเก็บไว้ในไฟล์ json ฉันจะไม่พูดถึงวิธีการทำงานฉันขอแนะนำให้ใช้ngx-translateแทนการแปลหลายเชิงมุม ดังนั้นหากคุณต้องการแปลงการแปลเป็นไฟล์ JSON มันค่อนข้างง่ายฉันใช้สคริปต์เทมเพลต T4 ที่แปลงไฟล์ Resources เป็นไฟล์ json ฉันขอแนะนำให้ติดตั้งตัวแก้ไข T4เพื่ออ่านไวยากรณ์และใช้อย่างถูกต้องเนื่องจากคุณต้องทำการแก้ไขบางอย่าง
สิ่งเดียวที่ควรทราบ: ไม่สามารถสร้างข้อมูลคัดลอกล้างข้อมูลและสร้างเป็นภาษาอื่นได้ ดังนั้นคุณต้องคัดลอกโค้ดด้านล่างหลาย ๆ ครั้งตามภาษาที่คุณมีและเปลี่ยนรายการก่อน "// เลือกภาษาที่นี่" ขณะนี้ไม่มีเวลาแก้ไข แต่อาจจะอัปเดตในภายหลัง (หากสนใจ)
เส้นทาง: 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 และคุณทำตามคำแนะนำของฉันในการสร้างโปรเจ็กต์หลายภาษาคุณจะต้องสร้างไฟล์ T4 สำหรับแต่ละโปรเจ็กต์ ตรวจสอบให้แน่ใจว่าไฟล์ json ถูกกำหนดอย่างมีเหตุผลไม่จำเป็นต้องเป็นen.json
เช่นนั้นก็สามารถเป็นexample-en.json
ได้ หากต้องการรวมไฟล์ json หลายไฟล์เพื่อใช้กับngx-translateให้ทำตามคำแนะนำที่นี่
ใช้ใน Xamarin Android
ตามที่อธิบายไว้ข้างต้นในการอัปเดตฉันใช้วิธีเดียวกับที่ทำกับ Angular / JSON แต่ Android ใช้ไฟล์ XML ดังนั้นฉันจึงเขียนไฟล์ T4 ที่สร้างไฟล์ XML เหล่านั้น
เส้นทาง: 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
โฟลเดอร์ดังนั้นด้านบนจึงเป็นภาษาอังกฤษสำหรับในvalues-en
โฟลเดอร์ แต่คุณต้องสร้างค่าเริ่มต้นที่จะเข้าไปในvalues
โฟลเดอร์ด้วย เพียงคัดลอกเทมเพลต T4 ด้านบนและเปลี่ยนโฟลเดอร์ในโค้ดด้านบน
เอาล่ะตอนนี้คุณสามารถใช้ไฟล์ทรัพยากรเดียวสำหรับโครงการทั้งหมดของคุณ สิ่งนี้ทำให้การส่งออกทุกอย่างไปยังเอกสารยกเว้นเป็นเรื่องง่ายมากและให้คนแปลและนำเข้าอีกครั้ง
ขอขอบคุณเป็นพิเศษกับส่วนขยาย VS ที่น่าทึ่งนี้ซึ่งใช้งานได้ดีกับresx
ไฟล์ ลองบริจาคให้เขาเพื่อผลงานที่ยอดเยี่ยมของเขา (ฉันไม่เกี่ยวอะไรกับเรื่องนี้ฉันแค่ชอบส่วนขยาย)