Используйте отдельный проект с ресурсами
Я могу сказать это по нашему опыту, имея текущее решение с 12 24 проектами, которые включают API, MVC, библиотеки проектов (основные функции), WPF, UWP и Xamarin. Стоит прочитать этот длинный пост, так как я думаю, что это лучший способ сделать это. С помощью инструментов VS легко экспортируется и импортируется для отправки в бюро переводов или проверки другими людьми.
РЕДАКТИРОВАТЬ 02/2018: По-прежнему сильна, преобразование ее в библиотеку .NET Standard позволяет даже использовать ее в .NET Framework и NET Core. Я добавил дополнительный раздел для преобразования его в JSON, чтобы, например, angular мог его использовать.
РЕДАКТИРОВАТЬ 2019: продвигаясь вперед с Xamarin, это по-прежнему работает на всех платформах. Например, Xamarin.Forms также советует использовать файлы resx. (Я еще не разрабатывал приложение на Xamarin.Forms, но документация, подробно описанная для начала, охватывает это: Документация Xamarin.Forms ). Так же, как преобразование в JSON, мы также можем преобразовать его в файл .xml для Xamarin.Android.
РЕДАКТИРОВАТЬ 2019 (2): при обновлении до UWP из WPF я обнаружил, что в UWP они предпочитают использовать другой тип файла .resw
, который идентичен с точки зрения содержимого, но использование отличается. Я нашел другой способ сделать это, который, на мой взгляд, работает лучше, чем решение по умолчанию .
РЕДАКТИРОВАТЬ 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 не отображает предложения intellisense и не отображает текст во время проектирования.
- Скажи мне.
Настроить
Создайте языковой проект в своем решении, дайте ему имя, например MyProject.Language . Добавьте в него папку с именем Resources и в этой папке создайте два файла ресурсов (.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 внутри заголовка Window или 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
файлами. Подумайте о том, чтобы пожертвовать ему за его потрясающую работу (я не имею к этому никакого отношения, мне просто нравится расширение).