Dziś z pewnych względów ciekawy wpis o tym jak – z deweloperskiego punktu widzenia – najłatwiej i najprzyjemniej tłumaczyć własną zawartość we względnie nowym frameworku jakim jest Orchard Framework. Oczywiście nie będzie mnie interesować w tym wpisie jak ma się sprawa tłumaczeń na poziomie użytkownika korzystającego z systemu – only developer issues ;]

Orchard Framework

Na wstępie jednak kilka słów na temat samego frameworka, bo o nim jeszcze na moim blogu nie było mowy. Orchard Framework jest to system zarządzania treścią dla platformy .NET, który powstał jakoś na początku 2011 roku i był dość mocno wspierany i promowany przez Microsoft. Ta promocja i zespół wsparcia wynikały z tego iż, Orchard miał wypromować nowe technologie tej samej firmy i pokazać ich realną wartość w realnym świecie; kwestia czy zostało to osiągnięte czy nie – jest już trudna do rozstrzygnięcia, jednak z mojego (zapewne ograniczonego) punktu widzenia, w dalszym ciągu Orchard nie jest w stanie nawet minimalnie zbliżyć się do najpopularniejszego na rynku WordPressa (przynajmniej jeśli chodzi o system klasy CMS). Ale nie o tym jest niniejszy artykuł ;]

Jeszcze z technicznego punktu widzenia: Orchard jest oparty na ASP.NET MVC 3; wykorzystuje Razor View Engine, zaś domyślny layout narzuca wykorzystanie HTML5, co jest niewątpliwie miłym akcentem – co daje nam faktycznie przegląd nowości w tym frameworku :)

Z początku nie byłem jakoś pozytywnie nastawiony do tego frameworka, jednak po nieco dokładniejszym przyjrzeniu się zastosowanym rozwiązaniom stwierdziłem, że framework ten jest co najmniej intrygujący.

Po pierwsze promuje technologie Microsoftu, a jako moduł dostępu do bazy danych wykorzystywany jest… nHibernate! Nie Entity Framework, tylko właśnie NH – ot, taki milutki akcent. Kolejna sprawa, która szczególnie mnie ujęła, to wykorzystanie zgoła odmiennego podejścia do tematu wielojęzyczności w .NET światku. Zamiast standardowych plików RESX rozrzuconych bezsensownie po całym projekcie, Orchard wprowadza obsługę standardu doskonale znanego z – żeby daleko nie szukać – PHP, Pythona i innych, a mianowicie czegoś co nazywa się gettext. Sama ta nazwa jest raczej mało znana – dlatego krótkie wyjaśnienie – chodzi o pliki *.po, które zawierają tłumaczenia. Pliki resx darzę szczególną nienawiścią ze względu na brak racjonalnych (czyt. dobrych) narzędzi do ich edycji. Potencjalne stwierdzenie, że VS daje do nich edytor, pozwolę sobie przemilczeć ;]

Z plikami resx jest też ponadto kolejny problem – są to pliki XML, które nie dość, że zawierają sporo redundantnego kodu opisującego notację tego języka (co jest w szczególności bolesne, gdy tych plików w projekcie jest dużo), to jeszcze powiedzmy, że nie nadają się idealnie do tematu jakim jest tłumaczenie zawartości na inne języki. Idąc dalej – korzystanie z tego konkretnego rozwiązania wymusza (nie zawsze, ale dość często) potrzebę kompilowania tychże plików, co wymaga użycia VS do tego by zmodyfikować tłumaczenia. Ostatnią zaś rzeczą, którą można byłoby wytknąć, to trzymanie się kurczowo kluczy tłumaczeń, czyli zapisu podobnego do notacji nazw zmiennych, po których szukamy naszych tłumaczeń *ORAZ *zapisu w domyślnym języku, co powiedzmy, że jest średnio ambitne. Żeby to zobrazować weźmy sobie taki przykład:

<!-- EN -->  
<data name="lblEmail.Text" xml:space="preserve">  
  <value>Please enter your e-mail:</value>
</data>  
<!-- FR -->  
<data name="lblEmail.Text" xml:space="preserve">  
  <value>Entrez votre e-mail ici:</value>
</data>  

Kluczem jest w tym wypadku nazwa kontrolki, której zawartość ma być tłumaczona. Nazwa “lblEmail.Text” nie jest jakoś nadmiarowo jasna. W przypadku gettext, można zwyczajowo jako klucz użyć angielski zwrot lub zdanie. Ma to bardzo dużą zaletę – nawet w przypadku braku pliku z jakimkolwiek tłumaczeniem (w tym też tym natywnym) nasza aplikacja będzie wyglądać i nie będzie zawierała tajemniczych skrótów i kluczy.

Kolejną dużą zaletą projektu gettext, jest duże ułatwienie programiście sposobu obsługiwania/wypełniania plików z tłumaczeniem oraz wywołania tłumaczenia z poziomu kodu aplikacji. W przypadku Orcharda, aby wyświetlić przetłumaczony kawałek tekstu wystarczy jedynie coś takiego:
@T(“text to translate”) lub T(“text to translate”), gdy chcemy zrobić to samo z poziomu kodu i albo zostanie wstawiony w to miejsce przetłumaczony tekst lub oryginalny, gdy nie ma go czym zastąpić.

To może teraz krótko na temat tego jak wygląda przykładowy plik *.po. Cały czas odnoszę się do przykładu zamieszczonego powyżej.

# język francuski 

#: ~/Modules/ModuleName/Views/SampleView.cshtml
#| msgid "Please enter your e-mail:"
msgid "Please enter your e-mail:"  
msgstr "Entrez votre e-mail ici:"

#: SampleApp.Orchard.Module.SampleController
#| msgid "E-mail"
msgid "E-mail"  
msgstr "E-mail"  

Do edycji jednak nie musimy używać zwykłego edytora tekstu tylko jednego z całkiem dobrych narzędzi w tym celu – ja preferuję POEdit. Ale nawet używając zwykłego edytora nie powinniśmy mieć problemów z edycją takich plików. Wiersze zaczynające się od # to wiersze z komentarzami; wiersze z #: na początku to wiersze z informacją o kontekście następnego rekordu. W przypadku Orcharda podajemy tam albo względną ścieżkę do pliku cshtml albo namespace + nazwę klasy, gdzie to tłumaczenie występuje. Jeśli nie podamy nic (lub tej linijki nie będzie) to wpis będzie traktowany jako globalny. msgid to klucz do tłumaczenia, a msgstr to tłumaczenie.

Ostatnia rzecz związana z wykorzystaniem gettext, to fakt iż na dobrą sprawę są narzędzia, które wspomagają nam pracę poprzez automatyczną generację plików z szablonem tłumaczeń poprzez przeparsowanie źródeł aplikacji i znalezieniu wszystkich wystąpień tłumaczeń w formatkach / widokach / kodzie. Jest też taki tool dla Orcharda, jednak jego funkcjonalność jest dość mocno ograniczona. Więcej o tym narzędziu można przeczytać i pobrać na stronie Vandelay w ustępie traktującym o Translation Manager.

Warto jeszcze na koniec wspomnieć o tym gdzie się w ogóle te pliki po przetrzymuje tak by Orchard mógł z nich bez problemu korzystać. Tłumaczenia możemy mieć – w skrócie – na poziomie modułu, motywu lub całego Orcharda. I tak, dla przykładu tłumaczenie naszego przykładowego motywu powinno być pod poniższą ścieżką:
~Themes/ContosoTheme/App_Data/Localization/pl-PL/orchard.theme.po

Początek korzystania może nie być trywialny, ale w momencie gdy już sporo rzeczy jest mniej więcej jasnych, to korzystanie z tego rozwiązania do tłumaczeń okazuje się naprawdę przyjemne, więc zdecydowanie polecam taką metodologię i to nie tylko w konktekście Orcharda. W referencjach zamieszczam jeden link pomocny przy wykorzystaniu gettexta w zwykłej aplikacji ASP.NET MVC.

Referencje

GNU Gettext na Wikipedii

Dokumentacja GetText

POEdit Website

Creating global ready applications / Orchard

i18n globalization with gettext and ASP.NET MVC