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:
Właściwości pliku Resources.resx muszą być:
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 Resources
wewnątrz XAML.
W pliku zasobów ustaw modyfikator dostępu na Publiczny:
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})" },
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 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.
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)
{
}
}
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 lang
tak:
<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:
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:Uid
kontrolę w języku XAML na klucz w zasobach. A w swoich zasobach musisz zrobić, Example.Text
aby wypełnić TextBlock
tekst. 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.cs
w 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";
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);
}
#>
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;
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 działa z values-xx
folderami, więc powyżej jest dla angielskiego w values-en
folderze. Ale musisz też wygenerować domyślne, które trafią do values
folderu. 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 resx
plikami. Rozważ przekazanie mu darowizny za jego niesamowitą pracę (nie mam z tym nic wspólnego, po prostu uwielbiam to rozszerzenie).
Widziałem projekty realizowane przy użyciu różnych podejść, z których każdy ma swoje zalety i wady.
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 .
źródło
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
źródło
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.
źródło
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!
źródło
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.
źródło
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).
źródło
Większość projektów open source używa do tego celu GetText . Nie wiem, jak i czy kiedykolwiek był używany w projekcie .Net.
źródło
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.
źródło
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
źródło