Используйте отдельный проект с ресурсами
Я могу сказать это по нашему опыту, имея текущее решение с 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
файлами. Подумайте о том, чтобы пожертвовать ему за его потрясающую работу (я не имею к этому никакого отношения, мне просто нравится расширение).
Я видел проекты, реализованные с использованием различных подходов, каждый из которых имеет свои достоинства и недостатки.
Я бы сказал, что выбранный вами ресурсный метод имеет большой смысл. Было бы интересно увидеть ответы других людей, так как я часто задаюсь вопросом, есть ли лучший способ делать такие вещи. Я видел множество ресурсов, которые все указывают на метод использования ресурсов, в том числе один прямо здесь, на SO .
источник
Я не думаю, что есть «лучший способ». Это действительно будет зависеть от технологий и типа создаваемого вами приложения.
Веб-приложения могут хранить информацию в базе данных, как предлагали другие плакаты, но я рекомендую использовать отдельные файлы ресурсов. Это файлы ресурсов, отделенные от вашего источника . Отдельные файлы ресурсов уменьшают конкуренцию за одни и те же файлы, и по мере роста вашего проекта вы можете обнаружить, что локализация будет выполняться отдельно от бизнес-логики. (Программисты и переводчики).
Гуру Microsoft WinForm и WPF рекомендуют использовать отдельные сборки ресурсов, настроенные для каждой локали.
Возможность WPF изменять размер элементов пользовательского интерфейса в соответствии с содержимым снижает объем требуемой работы с макетом, например: (японские слова намного короче английских).
Если вы рассматриваете WPF: я предлагаю прочитать эту статью msdn. Честно говоря, я нашел инструменты локализации WPF: msbuild, locbaml (и, возможно, электронную таблицу Excel) утомительными в использовании, но они действительно работают.
Что-то немного связанное: распространенная проблема, с которой я сталкиваюсь, - это интеграция устаревших систем, которые отправляют сообщения об ошибках (обычно на английском языке), а не коды ошибок. Это заставляет либо вносить изменения в унаследованные системы, либо отображать внутренние строки на мои собственные коды ошибок, а затем на локализованные строки ... да. Коды ошибок - друг локализаций
источник
+1 База данных
Формы в вашем приложении могут даже повторно переводиться на лету, если в базу данных вносятся исправления.
Мы использовали систему, в которой все элементы управления были сопоставлены в XML-файле (по одному на форму) с идентификаторами языковых ресурсов, но все идентификаторы были в базе данных.
По сути, вместо того, чтобы каждый элемент управления содержал идентификатор (реализация интерфейса или использование свойства tag в VB6), мы использовали тот факт, что в .NET дерево элементов управления было легко обнаружено с помощью отражения. Процесс при загрузке формы построит XML-файл, если он отсутствует. XML-файл будет сопоставлять элементы управления с их идентификаторами ресурсов, поэтому его просто нужно было заполнить и сопоставить с базой данных. Это означало, что не было необходимости изменять скомпилированный двоичный файл, если что-то не было помечено или если его нужно было разделить на другой идентификатор (некоторые слова на английском языке, которые могут использоваться как существительные и глаголы, возможно, потребуется перевести в два разных слова в словаре и не могут быть использованы повторно, но вы можете не обнаружить этого во время первоначального присвоения идентификаторов).
Единственное, где приложение принимает более активное участие, - это когда используется фаза с точками вставки.
Программное обеспечение для перевода базы данных было вашим основным экраном обслуживания CRUD с различными вариантами рабочего процесса, чтобы облегчить просмотр недостающих переводов и т. Д.
источник
Я искал и нашел это:
Если вы используете WPF или Silverlight, ваш подход может заключаться в использовании WPF LocalizationExtension по многим причинам.
Открытый исходный код IT БЕСПЛАТНО (и останется бесплатным) находится в очень стабильном состоянии
В приложении для Windows вы можете сделать что-то вроде этого:
public partial class App : Application { public App() { } protected override void OnStartup(StartupEventArgs e) { Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("de-DE"); ; Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("de-DE"); ; FrameworkElement.LanguageProperty.OverrideMetadata( typeof(FrameworkElement), new FrameworkPropertyMetadata( XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag))); base.OnStartup(e); } }
И я думаю, что на Wep Page подход может быть таким же.
Удачи!
источник
Я бы пошел с несколькими файлами ресурсов. Это не должно быть так сложно настроить. Фактически, я недавно ответил на аналогичный вопрос о настройке файлов ресурсов на основе глобального языка в сочетании с файлами ресурсов языка форм.
Локализация в Visual Studio 2008
Я считаю, что это лучший подход, по крайней мере, для разработки WinForm.
источник
Вы можете использовать коммерческие инструменты, такие как Sisulizer . Он создаст вспомогательную сборку для каждого языка. Единственное, на что следует обратить внимание, это не запутывать имена классов формы (если вы используете обфускатор).
источник
Большинство проектов с открытым исходным кодом используют для этой цели GetText . Я не знаю, как и использовался ли он раньше в проекте .Net.
источник
Мы используем настраиваемый поставщик для многоязыковой поддержки и помещаем все тексты в таблицу базы данных. Он работает хорошо, за исключением того, что мы иногда сталкиваемся с проблемами кеширования при обновлении текстов в базе данных без обновления веб-приложения.
источник
Стандартные файлы ресурсов проще. Однако, если у вас есть какие-либо данные, зависящие от языка, такие как таблицы поиска, вам придется управлять двумя наборами ресурсов.
Я этого не делал, но в своем следующем проекте я бы реализовал поставщика ресурсов базы данных. Я нашел, как это сделать в MSDN:
http://msdn.microsoft.com/en-us/library/aa905797.aspx
Я также нашел эту реализацию:
Поставщик DBResource
источник