Используйте отдельный проект с ресурсами
Я могу сказать это по нашему опыту, имея текущее решение с 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файлами. Подумайте о том, чтобы пожертвовать ему за его потрясающую работу (я не имею к этому никакого отношения, мне просто нравится расширение).