6

Planujemy dość dużą aplikację.Jak robić ekstremalne branding/internacjonalizacja w .NET

- Chcemy umiędzynarodowić naszą aplikację dla 30 krajów.

-W większości krajów dostępnych jest od 1 do 6 różnych marek.

-Każdy połączenie pewnej lokalizacji jak „de” i marki jak „XXX” może występować wiele razy więc potrzebujemy innego identyfikatora, aby dostać coś wyjątkowego:

"locale_brand_siteorigin" 

Dlatego mamy plik .resx takiego:

Konfiguracje. de.burgerkation.px10 .resx

Pogrubiony wydruk jest unikalnym identyfikatorem.

Podczas wykonywania tworzymy:

var rm = new ResourceManager("MyNamespace.Configurations.UniqueIdentifier",Assembly.GetExecuting()); 

zależności od naszej logiki biznesowej możemy stworzyć powyższy ResourceManager.

W końcu będziemy mieli ponad 180 plików resx ze wszystkimi kombinacjami unikalnego identyfikatora.

Czy znasz lepszy sposób na zrobienie tego rodzaju marki?

4 lata temu ktoś zadał to pytanie, ale żaden nie odpowiedział:

Industry standard for implementing application branding?

UPDATE

Chcę również rozszerzyć moje pytanie z prośbą o rozwiązanie pokazujące korzyści z korzystania z cultureandregioninfobuilder klasa, aby stworzyć tyle niestandardowych kultur.

https://msdn.microsoft.com/en-us/library/system.globalization.cultureandregioninfobuilder(v=vs.110).aspx

+0

Najprawdopodobniej będziesz musiał zaimplementować własne rozwiązanie, ale jest to proste. Tylko niektóre tabele w bazie danych z identyfikatorem zasobu, kulturą, marką (zerową), wartością, z powrotem do wartości domyślnych (nie każdy zasób powinien być zlokalizowany dla każdej marki). I oczywiście pamięć podręczna, która po uruchomieniu aplikacji w pamięci. Przechowywanie takich rzeczy razem z bazą kodu nie jest dobrym pomysłem - mogą wymagać aktualizacji o wiele więcej \ mniej często niż kod. – Evk

Odpowiedz

2

Nie polecam korzystania .resx pliki do takiego ogromnego projektu. Kiedy strona internetowa jest tłumaczona na wiele różnych języków, zwykle wiele osób jest zaangażowanych w zarządzanie kopiowaniem, tłumaczenie itp. Osoby te nie będą w stanie edytować plików .resx, ponieważ są one technicznie osadzone w kodzie aplikacji. Oznacza to, że twoi programiści będą musieli stale aktualizować zasoby za każdym razem, gdy pojawią się zmiany ... prawdziwy koszmar dla wszystkich.

Niedawno zbudowałem system oparty na bazie danych dla SumoSoft.Cms. Wszystkie łańcuchy mogą być zarządzane za pomocą panelu administracyjnego, podczas gdy w kodzie po prostu użyć:

@CmsMethods.StringContent("ContentSection_Name", "Fallback_Value") 

Ten Helper odpytuje bazę danych szukając podmiotu typu „ContentSection”, który jest zorganizowany mniej więcej tak:

public class ContentSection 
{ 
    public string Name { get; set; } 

    public ICollection<ContentSectionLocalizedString> LocalizedStrings { get; set; } 
} 

Każdy plik LocalizedString zawiera odniesienie do określonego kraju i właściwość "Treść", więc cała funkcja Pomocnika polega na wybraniu tej, która pasuje do aktualnej kultury wątku.

1

Rozszerzając @ FrancescoLorenzetti84 odpowiedź, jeden sposób robiłem to w przeszłości, aby łatwiej utrzymać się zawinąć odzyskiwanie bazy danych w klasie ResourceString tak, że można zrobić coś takiego:

private static readonly ResourceString res = "The value"; 

, a następnie odnieść się do tego w kodzie. Za sceną klasa ResourceString wykonuje pracę. Oto przykład, że:

namespace ResString 
{ 

    public interface IResourceResolver 
    { 
     string Resolve(string key, string defaultValue); 
    } 

    public class ResourceString 
    { 
     public ResourceString(string value) 
     { 
      this.defaultValue = value; 
      GetOwner(); 
     } 

     public string Value 
     { 
      get 
      { 
       if (!resolved) 
        Resolve(); 
       return value; 
      } 
     } 

     public override string ToString() 
     { 
      return Value; 
     } 

     public static implicit operator string(ResourceString rhs) 
     { 
      return rhs.Value; 
     } 

     public static implicit operator ResourceString(string rhs) 
     { 
      return new ResourceString(rhs); 
     } 

     protected virtual void Resolve() 
     { 
      if (Resolver != null) 
      { 
       if (key == null) 
        key = GetKey(); 
       value = Resolver.Resolve(key, defaultValue); 
      } 
      else 
      { 
       value = defaultValue; 
      } 
      resolved = true; 
     } 

     [MethodImpl(MethodImplOptions.NoInlining)] 
     protected virtual void GetOwner() 
     { 
      StackTrace trace = new StackTrace(); 
      StackFrame frame = null; 
      int i = 1; 
      while (i < trace.FrameCount && (owner == null || typeof(ResourceString).IsAssignableFrom(owner))) 
      { 
       frame = trace.GetFrame(i); 
       MethodBase meth = frame.GetMethod(); 
       owner = meth.DeclaringType; 
       i++; 
      } 
     } 

     protected virtual string GetKey() 
     { 
      string result = owner.FullName; 
      FieldInfo field = owner.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static).Where(f => 
       typeof(ResourceString).IsAssignableFrom(f.FieldType) && f.GetValue(null) == this 
      ).FirstOrDefault(); 
      if (field != null) 
       result += "." + field.Name; 
      return result; 
     } 


     public static IResourceResolver Resolver { get; set; } 

     private string defaultValue; 
     private string value; 

     private bool resolved; 
     private string key; 
     private Type owner; 


    } 
} 

i przykładowy program:

namespace ResString 
{ 
    class Program 
    { 
     /// <summary> 
     /// Description for the first resource. 
     /// </summary> 
     private static readonly ResourceString firstRes = "First"; 
     /// <summary> 
     /// Description for the second resource. 
     /// </summary> 
     private static readonly ResourceString secondRes = "Second"; 
     /// <summary> 
     /// Description for the format string. 
     /// </summary> 
     private static readonly ResourceString format = "{0} {1}"; 

     static void Main(string[] args) 
     { 
      ResourceString.Resolver = new French(); 
      Console.WriteLine(String.Format(format, firstRes, secondRes)); 
     } 

     private class French : IResourceResolver 
     { 
      public string Resolve(string key, string defaultValue) 
      { 
       switch (key) 
       { 
        case "ResString.Program.firstRes": 
         return "Premier"; 
        case "ResString.Program.secondRes": 
         return "Deuxième"; 
        case "ResString.Program.format": 
         return "{1} {0}"; 
       } 

       return defaultValue; 
      } 
     } 

    } 
} 

Jeśli prowadzisz, że to będzie wyjście: Deuxième Premier

komentarz na przypisanie przelicznika i będziesz get: Pierwsza sekunda

Dowolne miejsce, w którym można użyć ciągu znaków w interfejsie użytkownika, należy użyć zadeklarowanej metody ResourceString.

Zmiana przelicznika po rozwiązaniu wartości łańcuchowych nie zmieni ich wartości, ponieważ wartości są pobierane tylko raz. Będziesz oczywiście musiał napisać prawdziwy resolver, który pobiera z bazy danych.

Potrzebny jest program narzędziowy do uruchamiania skompilowanych klas i wyciągania deklaracji ResourceString oraz umieszczania klucza i wartości domyślnych w bazie danych lub pliku tekstowym, aby można je było przetłumaczyć. Powinno to również przejść przez wygenerowane pliki XML pomocy dla każdego zestawu i wyciągnąć komentarz do deklaracji ResourceString, aby tłumacz mógł korzystać z kontekstu. Kluczowe deklaracje będą również zawierać kontekst, ponieważ możesz łatwo grupować zasoby według klasy interfejsu użytkownika.

Dodaj to do skryptu budowy, upewnij się, że jest regularnie aktualizowany.

Możesz użyć tego samego podejścia do obrazów i tym podobnych.