Використовуйте окремий проект із ресурсами
Я можу сказати це з досвіду, маючи поточне рішення з 12 24 проектами, що включає API, MVC, бібліотеки проектів (основні функціональні можливості), WPF, UWP та Xamarin. Варто прочитати цю довгу публікацію, оскільки я вважаю, що це найкращий спосіб це зробити. За допомогою інструментів VS, які можна легко експортувати та імпортувати, надсилати до бюро перекладів або переглядати іншими людьми.
РЕДАКТУВАТИ 02/2018: Перетворення його на стандартну бібліотеку .NET дає змогу навіть використовувати її в .NET Framework та NET Core. Я додав додатковий розділ для перетворення його в JSON, щоб, наприклад, angular міг його використовувати.
EDIT 2019: Надалі з Xamarin це все ще працює на всіх платформах. Наприклад, Xamarin. Формує поради щодо використання файлів resx. (Я ще не розробляв додаток у Xamarin.Forms, але документація, яка є деталізованою, щоб просто розпочати, охоплює її: Xamarin.Forms Documentation ). Подібно до перетворення його в JSON, ми можемо також перетворити його у файл .xml для Xamarin.Android.
EDIT 2019 (2): Під час оновлення до UWP з WPF я зіткнувся з тим, що в UWP вони вважають за краще використовувати інший тип файлу .resw
, тобто з точки зору вмісту ідентичний, але використання різне. Я знайшов інший спосіб зробити це, який, на мій погляд, працює краще, ніж рішення за замовчуванням .
EDIT 2020: оновлено деякі пропозиції щодо більших (модульних) проектів, які можуть потребувати багатомовних проектів.
Отже, давайте дійдемо до цього.
Про
- Сильно друкується майже скрізь.
- У WPF вам не доводиться мати справу
ResourceDirectories
.
- Підтримується для ASP.NET, бібліотек класів, WPF, Xamarin, .NET Core, .NET Standard, наскільки я тестував.
- Не потрібні додаткові сторонні бібліотеки.
- Підтримує запасний варіант культури: en-US -> en.
- Не тільки бек-енд, він працює також у XAML для WPF та Xamarin.Forms, у .cshtml для MVC.
- Легко маніпулювати мовою, змінюючи
Thread.CurrentThread.CurrentCulture
- Пошукові системи можуть сканувати різними мовами, а користувач може надсилати або зберігати URL-адреси для певної мови.
Кон
- WPF XAML іноді глючить, нещодавно додані рядки не відображаються безпосередньо. Відновити - це тимчасове виправлення (vs2015).
- UWP XAML не відображає суперечливих пропозицій і не відображає текст під час проектування.
- Скажи мені.
Налаштування
Створіть мовний проект у своєму рішенні, дайте йому назву, як MyProject.Language . Додайте до неї папку, яка називається Ресурси, і в цій папці створіть два файли Ресурсів (.resx). Один з них називається Resources.resx, а інший - Resources.en.resx (або .en-GB.resx для конкретного). У моїй реалізації я використовую мову NL (голландська) як мову за замовчуванням, так що це йде у моєму першому файлі, а англійська йде у моєму другому файлі.
Налаштування має виглядати так:
Властивості Resources.resx мають бути:
Переконайтеся, що для спеціального простору імен інструментів встановлено простір імен проекту. Причиною цього є те, що у WPF ви не можете посилатися на Resources
внутрішній XAML.
А всередині файлу ресурсу встановіть модифікатор доступу на Public:
Якщо у вас такий великий додаток (скажімо, різні модулі), ви можете розглянути можливість створення декількох проектів, як зазначено вище. У цьому випадку ви можете додати до клавіш класів та класів ресурсів префікс певного модуля. Використовуйте найкращий редактор мов для Visual Studio, щоб об’єднати всі файли в єдиний огляд.
Використання в іншому проекті
Посилання на ваш проект: Клацніть правою кнопкою миші посилання -> Додати посилання -> 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; }
}
}
Якщо у вас є переробник, він автоматично перевірить, чи існує вказана назва ресурсу. Якщо ви віддаєте перевагу безпеці типу, ви можете використовувати шаблони T4 для створення переліку
Використання у WPF.
Звичайно, додайте посилання на ваш простір імен MyProject.Language , ми знаємо, як використовувати його у фоновому режимі.
У 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());
Останній рядок потрібен для оновлення інтерфейсу. Поки я все ще працюю над цим проектом, я помітив, що мені потрібно було зробити це 2 рази. Я можу закінчити вибір мови при першому запуску користувача. Але оскільки це розповсюджуватиметься через Магазин Windows, мова, як правило, дорівнює системній.
Потім використовуйте в XAML:
<TextBlock Text="{CustomResource ExampleResourceKey}"></TextBlock>
Використання його в Angular (конвертувати в JSON)
Зараз частіше зустрічається такий фреймворк, як Angular, у поєднанні з компонентами, тож без cshtml. Переклади зберігаються у файлах json, я не збираюся висвітлювати, як це працює, я б просто настійно рекомендував ngx-translate замість кутового мультиперекладу . Отже, якщо ви хочете перетворити переклади у файл JSON, це досить просто, я використовую скрипт шаблону T4, який перетворює файл ресурсів у файл 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 і змініть папку в наведеному вище коді.
Отже, тепер ви можете використовувати один єдиний файл ресурсів для всіх своїх проектів. Це дозволяє дуже легко експортувати все в документ excl, і нехай хтось перекладе його та імпортує знову.
Особлива подяка цьому чудовому розширенню VS, яке чудово працює з resx
файлами. Подумайте про те , щоб пожертвувати йому за його чудову роботу (я не маю до цього нічого спільного, я просто люблю продовження).