Лучший способ реализовать многоязычность / глобализацию в большом .NET проекте - PullRequest
64 голосов
/ 17 декабря 2008

Я скоро буду работать над большим проектом на c # и хотел бы с самого начала встроить многоязычную поддержку. Я поиграл и могу заставить его работать, используя отдельный файл ресурсов для каждого языка, а затем использовать менеджер ресурсов для загрузки строк.

Есть ли другие хорошие подходы, которые я мог бы рассмотреть?

Ответы [ 10 ]

41 голосов
/ 05 марта 2016

Используйте отдельный проект с ресурсами

Я могу сказать это по собственному опыту, имея текущее решение с 12 24 проектами, которое включает API, MVC, Библиотеки проектов (основные функции), WPF и Xamarin. Стоит прочитать этот длинный пост, так как я думаю, что это лучший способ сделать это. С помощью инструментов VS, легко экспортируемых и импортируемых для отправки в бюро переводов или просмотра другими людьми.

РЕДАКТИРОВАТЬ 02/2018: Преобразование в стандартную библиотеку .NET позволяет по-прежнему успешно использовать ее в .NET Framework и NET Core. Я добавил дополнительный раздел для преобразования его в JSON, чтобы, например, Angular мог его использовать.

РЕДАКТИРОВАНИЕ: 2019: В дальнейшем с Xamarin это все еще работает на всех платформах. Например. Xamarin.Forms также рекомендует использовать файлы resx. (Я еще не разрабатывал приложение в Xamarin.Forms, но документация, которая является подробным, чтобы только начать, охватывает его: Xamarin.Forms Documentation ). Точно так же, как преобразование его в JSON, мы также можем преобразовать его в XML-файл для Xamarin.Android (в настоящее время работает над ним).

Итак, давайте перейдем к нему.

Pro в

  • Сильно набран почти везде.
  • В WPF вам не нужно иметь дело с ResourceDirectories.
  • Поддерживается для ASP.NET, библиотек классов, WPF, Xamarin, .NET Core, .NET Standard, насколько я тестировал.
  • Никаких дополнительных сторонних библиотек не требуется.
  • Поддерживает резерв культуры: en-US -> en.
  • Не только бэкэнд, работает также в XAML для WPF и Xamarin.Forms, в .cshtml для MVC.
  • Легко манипулируйте языком, изменяя Thread.CurrentThread.CurrentCulture
  • Поисковые системы могут сканировать на разных языках, а пользователь может отправлять или сохранять URL-адреса для определенных языков.

Con в

  • WPF XAML иногда глючит, новые добавленные строки не отображаются напрямую. Перестройка - это временное исправление (vs2015).
  • Скажи мне.

Настройка

Создайте языковой проект в своем решении, присвойте ему имя, например MyProject.Language . Добавьте в него папку с именем Resources и в этой папке создайте два файла Resources (.resx). Один из них называется Resources.resx , а другой - Resources.en.resx (или .en-GB.resx для конкретных целей). В моей реализации я использую язык NL (голландский) в качестве языка по умолчанию, и это входит в мой первый файл, а английский - во второй.

Настройка должна выглядеть следующим образом:

language setup project

Свойства для Resources.resx должны быть: properties

Убедитесь, что для пространства имен настраиваемого инструмента задано пространство имен вашего проекта. Причиной этого является то, что в WPF нельзя ссылаться на Resources внутри XAML.

А внутри файла ресурсов установите модификатор доступа Public:

access modifier

Использование в другом проекте

Ссылка на ваш проект: щелкните правой кнопкой мыши на 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})" },   // en or en-US
    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 HandleErrorAttribute());
        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 просто устанавливает информацию о культуре.

//fixed number and date format for now, this can be improved.
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;
        //later, we will if-else the language here
        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: Потрясающий учебник, блог

Использование в библиотеках классов для моделей

Back-end использование такое же, но только пример использования в атрибутах

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 для создания перечисления

Использование в 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://default english because there can be so many different system language, we rather fallback on english in this case.
                MyProject.Language.Resources.Culture = new System.Globalization.CultureInfo("en-GB");
                break;
        }

    }
}

Использование его в Angular (преобразовать в JSON)

В наши дни более распространено использование фреймворка, такого как Angular, в сочетании с компонентами, то есть без cshtml. Переводы хранятся в файлах json, я не буду рассказывать, как это работает, но если вы хотите преобразовать это в файл JSON, это довольно просто, я использую шаблонный скрипт T4, который преобразует файл ресурсов в файл json. Я рекомендую установить T4 editor , чтобы прочитать синтаксис и правильно его использовать, потому что вам нужно внести некоторые изменения.

Следует отметить только одну вещь: невозможно сгенерировать данные, скопировать их, очистить данные и сгенерировать их для другого языка. Таким образом, вы должны скопировать приведенный ниже код столько раз, сколько у вас языков, и изменить запись перед тем, как «// выберите язык здесь». В настоящее время нет времени, чтобы исправить это, но, вероятно, будет обновляться позже (если интересно).

Путь: MyProject.Language / T4 / CreateWebshopLocalizationEN.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/CreateWebshopLocalizationEN.json";//choose language here
var fileResultPath = Path.Combine(Path.GetDirectoryName(this.Host.ResolvePath("")), "MyProject.Language", fileResultName);
//var fileDestinationPath = "../../MyProject.Web/ClientApp/app/i18n/";

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 }; //choose language here
string[] fileNamesDest = new string[] {fileNameDestEn }; //choose language here

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);
}


#>

Итак, теперь вы можете использовать один файл ресурсов для всех ваших проектов. Это позволяет очень легко экспортировать все в отдельный документ и позволить кому-то перевести его и импортировать снова.

19 голосов
/ 17 декабря 2008

Я видел проекты, реализованные с использованием различных подходов, каждый из которых имеет свои достоинства и недостатки.

  • Один сделал это в конфигурационном файле (не мой любимый)
  • Один сделал это с помощью базы данных - это работало довольно хорошо, но было больно, что вы знаете, что поддерживать.
  • Один из них использовал файлы ресурсов, как вы предлагаете, и я должен сказать, что это был мой любимый подход.
  • Самый простой сделал это, используя включаемый файл, полный строк - безобразно.

Я бы сказал, что выбранный вами метод ресурсов имеет большой смысл. Было бы интересно увидеть ответы других людей, так как я часто задаюсь вопросом, есть ли лучший способ сделать такие вещи. Я видел множество ресурсов, которые все указывают на метод использования ресурсов, включая один прямо здесь, на SO .

5 голосов
/ 17 декабря 2008

Я не думаю, что есть "лучший способ". Это действительно будет зависеть от технологий и типа приложения, которое вы создаете.

Веб-приложения могут хранить информацию в базе данных, как предлагали другие авторы, но я рекомендую использовать отдельные файлы ресурсов. То есть файлы ресурсов отделены от вашего источника . Отдельные файлы ресурсов снижают конкуренцию за одни и те же файлы, и по мере роста вашего проекта вы можете обнаружить, что локализация будет осуществляться отдельно от бизнес-логики. (Программисты и переводчики).

Microsoft WinForm и гуру WPF рекомендуют использовать отдельные сборки ресурсов, настроенные для каждой локали.

Способность WPF определять размер элементов пользовательского интерфейса для содержимого контента снижает объем требуемой компоновки, например: (японские слова намного короче английского).

Если вы рассматриваете WPF: Предлагаю прочитать эту статью MSDN Чтобы быть правдивым, я нашел инструменты локализации WPF: msbuild, locbaml (и, возможно, таблицу Excel) утомительными, но они работают.

Кое-что только слегка связанное: общая проблема, с которой я сталкиваюсь, заключается в интеграции устаревших систем, которые отправляют сообщения об ошибках (обычно на английском языке), а не коды ошибок. Это вынуждает либо вносить изменения в унаследованные системы, либо отображать строки бекенда в мои собственные коды ошибок, а затем в локализованные строки ... yech. Коды ошибок локализации друга

4 голосов
/ 17 декабря 2008

+ 1 База данных

Формы в вашем приложении могут даже перевести себя на лету, если будут внесены исправления в базу данных.

Мы использовали систему, в которой все элементы управления были сопоставлены в файле XML (по одному на форму) с идентификаторами языковых ресурсов, но все идентификаторы были в базе данных.

По сути, вместо того, чтобы каждый элемент управления содержал идентификатор (реализуя интерфейс или используя свойство тега в VB6), мы использовали тот факт, что в .NET дерево элементов управления было легко обнаружить с помощью отражения. Процесс, когда загруженная форма создаст файл XML, если он будет отсутствовать. XML-файл будет сопоставлять элементы управления с их идентификаторами ресурсов, поэтому его просто необходимо заполнить и сопоставить с базой данных. Это означало, что не было необходимости изменять скомпилированный двоичный файл, если что-то не было помечено, или если его нужно было разделить на другой идентификатор (некоторые слова в английском языке, которые могут использоваться как существительные, так и глаголы, возможно, придется переводить на два разных слова в словаре и не будет использоваться повторно, но вы можете не обнаружить это при первоначальном назначении идентификаторов). Но дело в том, что весь процесс перевода становится полностью независимым от вашего двоичного файла (каждая форма должна наследоваться от базовой формы, которая знает, как переводить себя и все свои элементы управления).

Единственное, где приложение становится более активным, это когда используется фаза с точками вставки.

Программное обеспечение для перевода баз данных было вашим основным экраном обслуживания CRUD с различными вариантами рабочего процесса, облегчающими просмотр пропущенных переводов и т. Д.

2 голосов
/ 02 декабря 2010

Я искал и нашел это:

Если вы используете 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 подход может быть таким же.

Удачи!

2 голосов
/ 17 декабря 2008

Вы можете использовать коммерческие инструменты, такие как Sisulizer . Это создаст спутниковую сборку для каждого языка. Единственное, на что следует обратить внимание, это не запутывать имена классов (если вы используете обфускатор).

2 голосов
/ 17 декабря 2008

Я бы пошел с несколькими файлами ресурсов. Это не должно быть так сложно настроить. Фактически я недавно ответил на аналогичный вопрос о настройке глобальных языковых файлов ресурсов в сочетании с файлами ресурсов языка форм.

Локализация в Visual Studio 2008

Я бы посчитал, что это лучший подход хотя бы для разработки WinForm.

0 голосов
/ 17 декабря 2008

Мы используем нестандартного провайдера для многоязычной поддержки и помещаем все тексты в таблицу базы данных. Это работает хорошо, за исключением того, что мы иногда сталкиваемся с проблемами кэширования при обновлении текстов в базе данных без обновления веб-приложения.

0 голосов
/ 17 декабря 2008

Стандартные файлы ресурсов проще. Однако если у вас есть какие-либо данные, зависящие от языка, такие как справочные таблицы, вам придется управлять двумя наборами ресурсов.

Я этого не делал, но в моем следующем проекте я бы реализовал поставщика ресурсов базы данных. Я нашел, как это сделать на MSDN:

http://msdn.microsoft.com/en-us/library/aa905797.aspx

Я также нашел эту реализацию:

DBResource Provider

0 голосов
/ 17 декабря 2008

В большинстве проектов с открытым исходным кодом для этой цели используется GetText . Я не знаю, как и использовался ли он когда-либо ранее в .Net проекте.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...