Najlepszy sposób na implementację wielojęzyczności / globalizacji w dużych projektach .NET

86

Wkrótce będę pracować nad dużym projektem C # i chciałbym od samego początku zbudować obsługę wielu języków. Dobrze się bawiłem i mogę go uruchomić, używając oddzielnego pliku zasobów dla każdego języka, a następnie użyj menedżera zasobów, aby załadować ciągi.

Czy są jakieś inne dobre podejścia, którym mógłbym się przyjrzeć?

tjjjohnson
źródło

Odpowiedzi:

88

Użyj osobnego projektu z zasobami

Mogę to powiedzieć z naszego doświadczenia, mając aktualne rozwiązanie z 12 24 projektami, które obejmują API, MVC, biblioteki projektów (podstawowe funkcje), WPF, UWP i Xamarin. Warto przeczytać ten długi post, bo uważam, że jest to najlepszy sposób. Za pomocą narzędzi VS można łatwo eksportować i importować do biur tłumaczeń lub recenzować przez inne osoby.

EDYCJA 02/2018: Nadal działa , przekonwertowanie go na bibliotekę .NET Standard umożliwia korzystanie z niej nawet w platformach .NET Framework i NET Core. Dodałem dodatkową sekcję do konwersji na JSON, więc na przykład angular może go używać.

EDYTUJ 2019: Idąc dalej z Xamarin, to nadal działa na wszystkich platformach. Np. Xamarin.Forms porady dotyczące używania plików resx, jak również. (Nie stworzyłem jeszcze aplikacji w Xamarin.Forms, ale dokumentacja, czyli szczegółowa, aby dopiero zacząć, obejmuje to: Dokumentacja Xamarin.Forms ). Podobnie jak przy konwersji do formatu JSON, możemy również przekonwertować go na plik .xml dla platformy Xamarin.Android.

EDYCJA 2019 (2): Podczas uaktualniania do platformy UWP z WPF napotkałem, że w UWP wolą używać innego typu pliku .resw, który jest identyczny pod względem zawartości, ale użycie jest inne. Znalazłem inny sposób na zrobienie tego, który moim zdaniem działa lepiej niż domyślne rozwiązanie .

EDYCJA 2020: Zaktualizowano niektóre sugestie dotyczące większych (modulair) projektów, które mogą wymagać projektów w wielu językach.

A więc przejdźmy do tego.

Pro's

  • Mocno napisane prawie wszędzie.
  • W WPF nie musisz się zajmować ResourceDirectories.
  • Obsługiwane dla ASP.NET, bibliotek klas, WPF, Xamarin, .NET Core, .NET Standard, o ile testowałem.
  • Nie są potrzebne żadne dodatkowe biblioteki innych firm.
  • Obsługuje rezerwę kultury: en-US -> en.
  • Nie tylko zaplecze, działa również w XAML dla WPF i Xamarin.Forms, w .cshtml dla MVC.
  • Łatwo manipuluj językiem, zmieniając Thread.CurrentThread.CurrentCulture
  • Wyszukiwarki mogą indeksować w różnych językach, a użytkownik może wysyłać lub zapisywać adresy URL specyficzne dla języka.

Cons

  • WPF XAML jest czasami błędny, nowo dodane ciągi nie pojawiają się bezpośrednio. Przebuduj to tymczasowa poprawka (vs2015).
  • UWP XAML nie wyświetla sugestii Intellisense i nie wyświetla tekstu podczas projektowania.
  • Powiedz mi.

Ustawiać

Utwórz projekt językowy w swoim rozwiązaniu, nadaj mu nazwę taką jak MyProject.Language . Dodaj do niego folder o nazwie Resources iw tym folderze utwórz dwa pliki Resources (.resx). Jeden o nazwie Resources.resx, a drugi o nazwie Resources.en.resx (lub .en-GB.resx). W mojej implementacji mam język NL (holenderski) jako język domyślny, więc jest to w moim pierwszym pliku, a angielski w drugim.

Konfiguracja powinna wyglądać następująco:

projekt konfiguracji języka

Właściwości pliku Resources.resx muszą być: nieruchomości

Upewnij się, że przestrzeń nazw narzędzia niestandardowego jest ustawiona na przestrzeń nazw projektu. Przyczyną tego jest to, że w WPF nie można odwoływać się do Resourceswewnątrz XAML.

W pliku zasobów ustaw modyfikator dostępu na Publiczny:

modyfikator dostępu

Jeśli masz tak dużą aplikację (powiedzmy różne moduły), możesz rozważyć tworzenie wielu projektów, jak powyżej. W takim przypadku możesz poprzedzić swoje klucze i klasy zasobów konkretnym Module. Użyj najlepszego dostępnego edytora języków dla programu Visual Studio, aby połączyć wszystkie pliki w jeden przegląd.

Wykorzystanie w innym projekcie

Odniesienie do projektu: kliknij prawym przyciskiem myszy References -> Add Reference -> Prjects \ Solutions.

Użyj przestrzeni nazw w pliku: using MyProject.Language;

Użyj tego w taki sposób na zapleczu: string someText = Resources.orderGeneralError; jeśli istnieje coś innego, co nazywa się Zasoby, po prostu umieść całą przestrzeń nazw.

Używanie w MVC

W MVC możesz ustawić język w dowolny sposób, ale ja użyłem sparametryzowanych adresów URL, które można skonfigurować w następujący sposób:

RouteConfig.cs Poniżej innych mapowań

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 (może być konieczne dodanie, jeśli tak, dodaj FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);do Application_start()metody wGlobal.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 po prostu ustawia informacje o kulturze.

//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)
    {

    }
}

Wykorzystanie w .cshtml

@using MyProject.Language;
<h3>@Resources.w_home_header</h3>

lub jeśli nie chcesz definiować zastosowań, po prostu wypełnij całą przestrzeń nazw LUB możesz zdefiniować przestrzeń nazw w /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>

Ten samouczek dotyczący źródła implementacji MVC: niesamowity blog z samouczkiem

Używanie w bibliotekach klas dla modeli

Korzystanie z zaplecza jest takie samo, ale tylko przykład użycia w atrybutach

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

Jeśli masz przekształcenie, automatycznie sprawdzi, czy podana nazwa zasobu istnieje. Jeśli wolisz bezpieczeństwo typów, możesz użyć szablonów T4 do wygenerowania wyliczenia

Używanie w WPF.

Oczywiście dodaj odniesienie do przestrzeni nazw MyProject.Language , wiemy, jak go używać na zapleczu.

W języku XAML, wewnątrz nagłówka Window lub UserControl, dodaj odwołanie do przestrzeni nazw o nazwie langtak:

<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">

Następnie wewnątrz etykiety:

    <Label x:Name="lblHeader" Content="{x:Static lang:Resources.w_home_header}" TextBlock.FontSize="20" HorizontalAlignment="Center"/>

Ponieważ jest wpisany silnie, masz pewność, że ciąg zasobów istnieje. Czasami może być konieczne ponowne skompilowanie projektu podczas instalacji, WPF czasami zawiera błędy z nowymi przestrzeniami nazw.

Jeszcze jedno dla WPF, ustaw język wewnątrz App.xaml.cs. Możesz wykonać własną implementację (wybierz podczas instalacji) lub pozostaw decyzję systemowi.

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

Używanie w UWP

W przypadku platformy UWP firma Microsoft korzysta z tego rozwiązania , co oznacza, że ​​trzeba będzie utworzyć nowe pliki zasobów. Ponadto nie można ponownie użyć tekstu, ponieważ chcą oni ustawić x:Uidkontrolę w języku XAML na klucz w zasobach. A w swoich zasobach musisz zrobić, Example.Textaby wypełnić TextBlocktekst. W ogóle nie podobało mi się to rozwiązanie, ponieważ chcę ponownie wykorzystać moje pliki zasobów. W końcu wpadłem na następujące rozwiązanie. Właśnie dowiedziałem się o tym dzisiaj (26.09.2019), więc mogę wrócić z czymś innym, jeśli okaże się, że to nie działa zgodnie z oczekiwaniami.

Dodaj to do swojego projektu:

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

Dodaj to do App.xaml.csw konstruktorze:

CustomXamlResourceLoader.Current = new MyXamlResourceLoader();

Gdziekolwiek chcesz w swojej aplikacji, użyj tego, aby zmienić język:

ApplicationLanguages.PrimaryLanguageOverride = "nl";
Frame.Navigate(this.GetType());

Ostatni wiersz jest potrzebny do odświeżenia interfejsu użytkownika. Podczas gdy nadal pracuję nad tym projektem, zauważyłem, że muszę to zrobić 2 razy. Mogę skończyć z wyborem języka przy pierwszym uruchomieniu użytkownika. Ale ponieważ będzie to dystrybuowane za pośrednictwem Sklepu Windows, język jest zwykle równy językowi systemu.

Następnie użyj w XAML:

<TextBlock Text="{CustomResource ExampleResourceKey}"></TextBlock>

Używanie go w Angular (konwersja do JSON)

Obecnie bardziej powszechne jest posiadanie frameworka takiego jak Angular w połączeniu z komponentami, więc bez cshtml. Tłumaczenia są przechowywane w plikach json, nie zamierzam opisywać, jak to działa, po prostu bardzo polecam ngx-translate zamiast wielojęzycznego tłumaczenia kątowego. Więc jeśli chcesz przekonwertować tłumaczenia na plik JSON, jest to całkiem proste, używam skryptu szablonu T4, który konwertuje plik Resources na plik JSON. Zalecam zainstalowanie edytora T4, aby odczytać składnię i używać jej poprawnie, ponieważ musisz wprowadzić pewne modyfikacje.

Tylko jedna uwaga: nie ma możliwości generowania danych, kopiowania ich, czyszczenia danych i generowania ich dla innego języka. Musisz więc skopiować poniższy kod tyle razy, ile masz języków i zmienić wpis przed „// choose language here”. Obecnie nie ma czasu na naprawianie tego, ale prawdopodobnie zaktualizuję później (jeśli zainteresowany).

Ścieżka: 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";//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);
}


#>

Jeśli masz aplikację modulair i postąpiłeś zgodnie z moją sugestią tworzenia projektów w wielu językach, będziesz musiał utworzyć plik T4 dla każdego z nich. Upewnij się, że pliki json są logicznie zdefiniowane, nie musi en.json, ale może też być example-en.json. Aby połączyć wiele plików json do użycia z ngx-translate , postępuj zgodnie z instrukcjami tutaj

Użyj w Xamarin.Android

Jak wyjaśniono powyżej w aktualizacjach, używam tej samej metody, co w przypadku Angular / JSON. Ale Android używa plików XML, więc napisałem plik T4, który generuje te pliki XML.

Ścieżka: 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;
                    //if(!name.ToString().Contains("WEBSHOP_") && !name.ToString().Contains("DASHBOARD_"))//only include keys with these prefixes, or the country ones.
                    //{
                    //  if(name.ToString().Length != 2)
                    //  {
                    //      continue;
                    //  }
                    //}
                    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("&", "&amp;");
                     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 działa z values-xxfolderami, więc powyżej jest dla angielskiego w values-enfolderze. Ale musisz też wygenerować domyślne, które trafią do valuesfolderu. Po prostu skopiuj powyższy szablon T4 i zmień folder w powyższym kodzie.

Proszę bardzo, możesz teraz używać jednego pliku zasobów dla wszystkich swoich projektów. Dzięki temu bardzo łatwo jest wyeksportować wszystko do dokumentu excl i pozwolić komuś przetłumaczyć go i ponownie zaimportować.

Specjalne podziękowania dla tego niesamowitego rozszerzenia VS, które działa niesamowicie z resxplikami. Rozważ przekazanie mu darowizny za jego niesamowitą pracę (nie mam z tym nic wspólnego, po prostu uwielbiam to rozszerzenie).

CularBytes
źródło
1. Jak dodać więcej języków po wdrożeniu ?, dodając więcej zasobów do Mylanguage i ponownie skompiluj, a następnie ponownie wdróż? (2) Czy jest jakieś IDE lub narzędzie, którego użyłeś do tłumaczenia (mam ponad 40 formularzy w winform), czy możesz się nimi podzielić? (3) Próbowałem i nie otrzymałem tłumaczenia, kiedy zmieniłem kulturę na arabską, utworzę pytanie i dam ci znać
Smith
5
1: Po prostu opublikuj Resources.xx.resx i zmień xx i wszędzie tam, gdzie mam instrukcje przełącznika, dodaj język. 2 Używam rozszerzenia ResxManager (autorstwa TomEnglert), można go zainstalować za pomocą Narzędzia -> Rozszerzenia, dobry sposób na umieszczenie języków obok siebie, ale rzeczywiste tłumaczenie jest wykonywane przez kogoś innego (łatwy eksport i import do / z programu ResxManager). 3. Wystąpił również ten ostatni raz przez dodanie jednego, sprawdź wszystkie pliki, które są wymienione powyżej, ale niektóre punkty przerwania. Nie używałem tego jednak w WinForms.
CularBytes
13
Czy mogę zagłosować za własną odpowiedzią? Po prostu musiałem głupio sprawdzić własną odpowiedź, aby naprawić błąd w przełączaniu między językami w WPF -.-
CularBytes
1
@TiagoBrenck Cześć, tylko w tej konkretnej sytuacji, jeśli angielski jest językiem domyślnym, możesz utworzyć plik resources.en-gb.resx i dodać tam zmiany 1%. Domyślnie, jeśli wybrany jest język en-gb, a słowa nie są tłumaczone, używany jest język domyślny (resources.resx).
CularBytes
1
Jak zorganizowałeś klucze w pliku zasobów? Ponieważ widzę, że używasz tylko jednego pliku zasobów i według ciebie masz ogromną aplikację, jak podzieliłeś klucze? Coś w rodzaju „Feature.MyMessage” lub „Area.Feature.MyMessage”
Francesco Venturini
21

Widziałem projekty realizowane przy użyciu różnych podejść, z których każdy ma swoje zalety i wady.

  • Jeden zrobił to w pliku konfiguracyjnym (nie mój ulubiony)
  • Jeden zrobił to za pomocą bazy danych - działało to całkiem nieźle, ale było uciążliwe, gdy wiesz, co należy konserwować.
  • Jeden wykorzystał pliki zasobów w sposób, który sugerujesz i muszę powiedzieć, że było to moje ulubione podejście.
  • Najbardziej podstawowy zrobił to używając pliku nagłówkowego pełnego ciągów - brzydki.

Powiedziałbym, że wybrana przez Ciebie metoda zasobów ma duży sens. Byłoby interesujące zobaczyć odpowiedzi innych ludzi, ponieważ często zastanawiam się, czy istnieje lepszy sposób robienia takich rzeczy. Widziałem wiele zasobów, które wszystkie wskazują na metodę using resources, w tym jeden tutaj na SO .

BenAlabaster
źródło
5

Nie sądzę, że istnieje „najlepszy sposób”. To naprawdę będzie zależeć od technologii i rodzaju aplikacji, którą tworzysz.

Aplikacje internetowe mogą przechowywać informacje w bazie danych, tak jak sugerowały inne postery, ale zalecam używanie oddzielnych plików zasobów. To są pliki zasobów oddzielne od twojego źródła . Oddzielne pliki zasobów zmniejszają rywalizację o te same pliki, a wraz z rozwojem projektu może się okazać, że lokalizacja zostanie przeprowadzona oddzielnie od logiki biznesowej. (Programiści i tłumacze).

Guru Microsoft WinForm i WPF zalecają używanie oddzielnych zestawów zasobów dostosowanych do poszczególnych ustawień regionalnych.

Zdolność WPF do dopasowywania elementów UI do zawartości obniża wymaganą pracę nad układem, np .: (japońskie słowa są znacznie krótsze niż angielskie).

Jeśli zastanawiasz się nad WPF: proponuję przeczytanie tego artykułu w msdn. Szczerze mówiąc, uznałem, że narzędzia lokalizacyjne WPF: msbuild, locbaml (i być może arkusz kalkulacyjny programu Excel) są żmudne w użyciu, ale działają.

Coś tylko nieznacznie powiązanego: Częstym problemem, z jakim się spotykam, jest integracja starszych systemów, które wysyłają komunikaty o błędach (zwykle w języku angielskim), a nie kody błędów. Wymusza to albo zmiany w starszych systemach, albo mapowanie ciągów zaplecza na moje własne kody błędów, a następnie na zlokalizowane łańcuchy ... tak. Kody błędów są przyjacielem lokalizacji

MW_dev
źródło
4

Baza danych +1

Formularze w Twojej aplikacji mogą nawet zostać ponownie przetłumaczone w locie, jeśli w bazie danych zostaną wprowadzone poprawki.

Użyliśmy systemu, w którym wszystkie kontrolki były mapowane w pliku XML (po jednym na formularz) na identyfikatory zasobów języka, ale wszystkie identyfikatory znajdowały się w bazie danych.

Zasadniczo zamiast trzymać identyfikator w każdej kontrolce (implementując interfejs lub używając właściwości tag w VB6), wykorzystaliśmy fakt, że w .NET drzewo kontrolne było łatwo wykrywalne poprzez odbicie. Proces, w którym załadowany formularz utworzyłby plik XML, gdyby go brakowało. Plik XML mapowałby kontrolki na ich identyfikatory zasobów, więc wystarczyło to wypełnić i zmapować do bazy danych. Oznaczało to, że nie było potrzeby zmieniania skompilowanego pliku binarnego, jeśli coś nie zostało oznaczone lub jeśli trzeba było to podzielić na inny identyfikator (niektóre słowa w języku angielskim, które mogą być używane zarówno jako rzeczowniki, jak i czasowniki, mogą wymagać przetłumaczenia na dwa różne słowa w słowniku i nie mogą zostać ponownie użyte, ale możesz nie odkryć tego podczas początkowego przypisywania identyfikatorów).

Jedyne, w których aplikacja jest bardziej zaangażowana, to faza z punktami wstawiania.

Oprogramowanie do tłumaczenia bazy danych było podstawowym ekranem konserwacji CRUD z różnymi opcjami przepływu pracy, aby ułatwić przeglądanie brakujących tłumaczeń itp.

Cade Roux
źródło
Podoba mi się twoja metoda, czy możesz mi powiedzieć, w jaki sposób generowany jest plik xml, a następnie mapowany do bazy danych?
Smith
W Form_Load lub czymkolwiek, funkcja tłumaczenia została wywołana na instancji formularza. Załadowano plik XML dla tego formularza. Jeśli nie istniał, został utworzony. Nie mam schematu od ręki, ale w zasadzie zamapował on nazwę kontrolną w formularzu na identyfikator tłumaczenia. Zatem każda kontrolka w formularzu, której nie było w pliku XML, otrzymałaby wpis bez identyfikatora tłumaczenia. Ponieważ byłyby tworzone na żądanie, można po prostu utworzyć formularz, uruchomić aplikację, która utworzy lub zaktualizuje plik XML pod kątem brakujących kontrolek. Następnie podaj identyfikatory tłumaczeń elementów.
Cade Roux
4

Szukałem i znalazłem to:

Jeśli używasz WPF lub Silverlight, Twoje podejście może być używane z wielu powodów przy użyciu WPF LocalizationExtension .

IT''s Open Source To jest DARMOWE (i pozostanie wolne) jest w naprawdę stabilnym stanie

W aplikacji Windows możesz zrobić coś takiego:

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

I myślę, że na WEP Page podejście może być takie samo.

Powodzenia!

JxXx
źródło
2

Poszedłbym z wieloma plikami zasobów. Konfiguracja nie powinna być taka trudna. W rzeczywistości niedawno odpowiedziałem na podobne pytanie dotyczące ustawiania globalnych plików zasobów opartych na języku w połączeniu z plikami zasobów języka formularzy.

Lokalizacja w Visual Studio 2008

Uznałbym to za najlepsze podejście przynajmniej do rozwoju WinForm.

KMessenger
źródło
Wadą zasobów jest konieczność ponownego uruchomienia, aby zmienić język. Prawdopodobnie dla większości jest to akceptowalne, ale to moja irytacja ...
Roman Starkov
2

Możesz użyć komercyjnych narzędzi, takich jak Sisulizer . Stworzy asembler satelitarny dla każdego języka. Jedyne, na co powinieneś zwrócić uwagę, to nie zaciemniać nazw klas w formularzach (jeśli używasz obfuscator).

Davorin
źródło
0

Większość projektów open source używa do tego celu GetText . Nie wiem, jak i czy kiedykolwiek był używany w projekcie .Net.

Wasilij
źródło
0

Korzystamy z niestandardowego dostawcy obsługi wielu języków i umieszczamy wszystkie teksty w tabeli bazy danych. Działa dobrze, z wyjątkiem tego, że czasami napotykamy problemy z buforowaniem podczas aktualizacji tekstów w bazie danych bez aktualizacji aplikacji internetowej.

Cossintan
źródło
0

Standardowe pliki zasobów są łatwiejsze. Jeśli jednak masz jakiekolwiek dane zależne od języka, takie jak tabele wyszukiwania, będziesz musiał zarządzać dwoma zestawami zasobów.

Nie zrobiłem tego, ale w moim następnym projekcie zaimplementowałbym dostawcę zasobów bazy danych. Znalazłem, jak to zrobić w MSDN:

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

Znalazłem również tę implementację:

Dostawca DBResource

Ben Dempsey
źródło