Wielojęzyczność aplikacji www można rozwiązać na kilka sposobów. Jedne strony mają rysuneczki flag symbolizujących język, w jakim chcemy widzieć teksty (i nie tylko) i pamiętają to w cookie. Inne pozwalają to ustawić w profilu użytkownika i pamiętają ustawienie w bazie.
Ostatnio pisałem rozwiązanie, które ustawia odpowiednią kulturę aplikacji na podstawie informacji wysyłanych przez przeglądarkę podczas żądania. Sam szkielet rozwiązania jest banalny:
1: public static class CultureUtils 2: { 3: public static void SetCurrentThreadCultureToBrowserPreferences() 4: { 5: var langs = HttpContext.Current.Request.UserLanguages; 6: 7: CultureInfo userCulture = CultureInfo.GetCultureInfo(langs[0]); 8: 9: Thread.CurrentThread.CurrentCulture = userCulture; 10: Thread.CurrentThread.CurrentUICulture = userCulture; 11: } 12: }
Pobieramy język wybrany jako pierwszy w ustawieniach przeglądarki… i już.
Jednak podczas działania aplikacji raz po raz coś zaczęło się wywalać. Problemem okazał się POST wysyłany przez flashowy uploadify – takie żądanie nie wysyłało UserLanguages. Więc pierwsza modyfikacja…
1: public static void SetCurrentThreadCultureToBrowserPreferences() 2: { 3: var langs = HttpContext.Current.Request.UserLanguages; 4: 5: if (langs != null) 6: { 7: CultureInfo userCulture = CultureInfo.GetCultureInfo(langs[0]); 8: 9: //....
Nie trwało jednak długo, zanim znalazłem kolejny problem. Otóż w przeglądarce użytkownik może przecież wybrać tzw “neutral culture”, czyli kulturę nie określającą regionu, do którego się odnosi (więcej info w MSDN). Przy próbie ustawienia takiej kultury jako UICulture dostajemy wyjątek:
“Culture ‘en’ is a neutral culture. It cannot be used in formatting and parsing and therefore cannot be set as the thread’s current culture”
I słusznie…
Niektóre aplikacje mogłyby sobie pozwolić na wyświetlenie w tym momencie komunikatu “wybierz w przeglądarce ‘specific’ culture”. Jest jedno ALE… nie we wszystkich przeglądarkach da się to zrobić! Na przykład domyślnie w FF 4.0 nie ma kultury “pl-PL”, jest tylko “pl”. Wymaganie od użytkownika żeby sam definiował nową kulturę na poziomie przeglądarki to trochę przegięcie.
Wystarczyło jednak kilka chwil zastanowienia i taką sytuację rozwiązałem w następujący sposób:
1: public static class CultureUtils 2: { 3: private static readonly CultureInfo[] _allCultures = CultureInfo.GetCultures(CultureTypes.FrameworkCultures); 4: 5: public static void SetCurrentThreadCultureToBrowserPreferences() 6: { 7: var langs = HttpContext.Current.Request.UserLanguages; 8: 9: if (langs != null) 10: { 11: CultureInfo userCulture = CultureInfo.GetCultureInfo(langs[0]); 12: if (userCulture.IsNeutralCulture) 13: { 14: // browser sent neutral culture that cannot be used for parsing and formatting 15: // using first non-neutral culture inheriting from this culture 16: userCulture = _allCultures.Where(x => x.Parent.LCID == userCulture.LCID) 17: .First(); 18: } 19: Thread.CurrentThread.CurrentCulture = userCulture; 20: Thread.CurrentThread.CurrentUICulture = userCulture; 21: } 22: } 23: }
Po prostu w przypadku otrzymania neutralnej kultury wyszukuję pierwszą lepszą kulturę specific będącą jej dzieckiem. A że nie znalazłem innego sposobu niż ten przedstawiony wyżej, to zostawiam go tu ku pamięci.
Krótki, rzeczowy wpis – na pewno się przyda – dzięki
Hej,
nie tylko user może wyłączyć domyślny język przeglądarki, googlebot pewnie też nie ma ustawionego domyślnego języka ;)
Sądzę, że warto także sprawdzić czy HttpContext.Current nie jest przypadkiem null, tak dla pewności.
Inna rzecz to podejżewam, że nie masz testu na ten kod ;)
Pozdrawiam,
Marek
marek,
To prawda, na pewno w niektórych sytuacjach trzeba do tego coś dopisać. Akurat aplikacja w której zastosowałem powyższy kod nie jest wystawiana publicznie, więc google (czy cokolwiek innego poza przeglądarką) mnie nie interesuje.
A co do testu – to prawda, nie mam i w obecnej postaci raczej ciężko byłoby takowy dopisać. Ale ten kod ma pokazać technikę ustawiania kultury aplikacji na podstawie danych z przeglądarki, a nie testowania aplikacji webowych:).
Jakbym dorzucił tu przekazywanie kontekstu w parametrze konstruktora to trzeba by było dodatkowo napisać skąd instancja tej klasy się bierze, gdzie wpiąć kontener DI, jaki to ma wpływ na ControllerFactory i wiele innych rzeczy, o których tutaj pisać nie chciałem.
Czy był jakiś konkretny powód, dla którego nie chciałeś wykorzystać <globalization /> z web.config (http://msdn.microsoft.com/en-us/library/hy4kkhe0(v=VS.90).aspx)? Dla każdej neutral culture przypisanej do CurrentUICulture asp.net sam wybierze pierwszy specific culture i przypisze do CurrentCulture.
Michal,
Ustawiając to w web.config decyduję się na jedną stałą kulturę… czy czegoś nie wiem?
procent,
Można go ustawić tak: <globalization culture="auto" uiCulture="auto"/> i wtedy pobierze sobie to z przeglądarki użytkownika. Moim zdaniem zadziała to dokładnie tak jak Twój kod, czyli odpowiednio pominie netural-culture dla CurrentCulture.
Można go ustawić tak: <globalization culture="auto" uiCulture="auto"/>
Chyba nawet z tego skorzystać ,niż powyższej klasy ,gdyż można łatwo podczepić
culture z web.config np.pod scriptmenagera ustawiając
EnableScriptGlobalization="true" EnableScriptLocalization="true"
Michal, xRidx,
Dzięki za info, nie zdawałem sobie z tego sprawy. Jeśli tak jest to faktycznie lepiej skorzystać z auto niż z tego co napisałem:)
Ja to tak ogólnie nigdy nie zmuszam użytkowników do danego języka, zdarza się, że pod ręką jest przeglądarka z innym domyślnym językiem niż pożądany albo ktoś po prostu woli przeglądać w innym języku, tak więc zawsze dorzucam te flagi języka, tak na wszelki wypadek, lepsze to niż przełączać priorytety kultury w przeglądarce, przynajmniej dla użytkownika końcowego.
endrju,
Tak jak napisałem nie jest to aplikacja publiczna i było to konkretne "feature request" od klienta. W aplikacji publicznej z pewnością znalazłby się inny mechanizm do tego.