Symfony Translations

Językiem interfejsu, który najcześciej przygotowuję jest angielski lub polski. Jednak coraz cześciej zdarza mi się realizować multijęzykowe aplikacje. Symfony od zawsze miało bardzo fajne rozwiązanie do tworzenia i zarządzania tłumaczeniami, do którego twig również jest przystosowany. Dzisiaj chcę pokazać Ci jak tworzyć aplikacje, których interfejs posiada wiele wersji językowych i jak używać komponentu Symfony do tego przeznaczonego czyli Symfony translations.

Kiedy chcemy stworzyć wielojęzyczną aplikacje musimy pomyśleć o kilku rzeczach. Pierwsza podstawowa to tłumaczenia. Jak je zrealizować, gdzie trzymać itd. Druga jak rozpoznać, który język wczytać danemu użytkownikowi. Trzecia, opcjonalna, czy powinno to wpłynąć na routing. Zacznijmy jednak od instalacji.

Instalacja Symfony translations

Aby wprowadzić wiele języków interfejsu wystarczy nam jedynie jedna paczka.

composer require symfony/translation

Kiedy już ją zainstalujemy musimy ją skonfigurować. Stworzyć plik config/packages/translation.yaml jeśli go nie ma lub jeśli jest, to jedynie uzupełnić:

framework:
    default_locale: 'en'
    translator:
        fallbacks: ['en']

Default locale oznacza, że jeśli dany user nie będzie miał informacji o języku to zostanie mu pokazany angielski interfejs. Fallback określa, że jeśli user ma określony język ale nie może dla danego klucza znaleść tłumaczenia to pokaże angielską wersję.

Słowniki

Symfony ma bardzo prostą zasadę szukania fraz w wielu plikach z tłumaczeniami. Takie pliki mają oczywiście określoną strukturę nazewnictwa czyli *.locale.yaml/xlf/php np. messages.fr_FR.yaml lub messages.fr.yaml. Ja zdecydowanie preferuję trzymać tłumaczenia w plikach yaml, ponieważ ta struktura jest dla mnie najbardziej czytelna.

Nie bez powodu podałam nazwę messages. To właśnie w plikach messages Symfony szuka tłumaczeń fraz, jest to domyślna nazwa słownika. Można jednak stworzyć sobie wiele słowników w zależności od kontekstu(domen) np. users, admin, mail, base itp. Jednak wtedy należy pamiętać aby podczas stosowania translacji podać w jakiej domenie Symfony ma go szukać.

<label>{% trans from 'users' %} surname {% endtrans %}</label>
lub
<th>{{ 'surname'|trans({}, 'users') }}</th>
lub 
$title = $this->translator->trans('Hello World!',[],'users');

Pliki ze słownikami możemy trzmać w wielu miejscach, jednak musimy pamiętać o chronologii. Kolejność w jakiej Symfony sprawdza słowniki i co ważne, jak znajdzie kończy szukać więc powtarzające się tłumaczenie zostanie zaczytane z pierwszego pliku jaki znajdzie to.

  1. translations/ w głównym folderze.
  2. src/Resources/<bundle name>/translations/ zauważ, że to nie folder bundla, a Resources w głównym src i dopiero w nim jest nazwa bundla.
  3. Resources/translations/ w każdym bundlu po kolei, oczywiście jeśli jeszcze z nich korzystasz.

Jak tłumaczyć tekst?

W zależności od tego czy tłumaczymy teksty w kontrolerze, serwisie czy templatce wyglądać to będzie trochę inaczej. Aby przetłumaczyć frazę w kontrolerze lub serwisie użyjemy DI i wstrzykniemy Symfony\Contracts\Translation\TranslatorInterface do danej klasy. Następnie wystarczy zrobić tak:

$title = $this->translator->trans('Hello World!');

W szablonie twig możemy zrobić to na kilka sposobów. Pierwszy z ich, dłuższy wygląda tak:

<h1>{% trans %}Hello World!{% endtrans %}</h1>

Ewentualnie krótsza forma:

{{ 'Hello World!'|trans }}

Przy zwykłych frazach, bez zmiennych między tymi dwoma sposobami nie ma różnicy. Kiedy jednak mamy zmienne we frazie zaczyna się tworzyć rozbieżność.

Bardzo często chcemy przetłumaczyć jakiś tekst, który zawiera w sobie zmienne. Jak to zrobić? Bardzo prosto. Jednym ze sposobów, trochę haksiorskim, jest złączenie przetłumaczonego stringa ze zmienną. tego nawet Ci nie pokażę. Nie rób tego. Poprawnym sposobem jest po prostu użycie tej zmiennej we frazie tylko umieszczenie jej dwóch znakach %.

$title = $this->translator->trans('I love %something%', [%something% => $something,]);
lub
{{ 'I love %something%'|trans({ '%something%': variableName }) }}

Tutaj pojawia się różnica między tłumaczeniami w twigu. Nie znam rozwiązania, który pozwoliłby na takie tłumaczenia zmiennych w zapisie {% trans %}{% endtrans %}. Oczywiście mogę się mylić i jeśli znasz taki zapis to chętnie go poznam, napisz w komentarzu.

Ważną różnicą między tymi dwoma zapisami jest również to, że zapis |trans robi escape wartości przez co jest bezpieczniejszy.

Frazy to nie zawsze frazy

Tekst który tłumaczymy może być zwykłym zdaniem lub zdaniami zapisanymi w języku angielskim. Jednak nie tylko. Może być także kluczem np.

{{ 'event.save.success'|trans() }}

Plusem stosowania kluczy jest to, że zapis w słowniku yaml może wyglądać tak:

event:
   save:
      success: Wydarzenie zostało zapisane.
      failed: Wydarzenie nie zostało zapisane. Wystąpił błąd.

Routing

Skoro wiesz już jak przygotować tłumaczenia interfejsu to na pewno interesuje Cię teraz jak to spiąć z userem. Wystarczy skonfigurować odpowiednio ścieżki. Najpierw ustaw dopuszczalne języki w pliku config/routes/annotations.yaml

controllers:
    resource: ../../src/Controller/
    type: annotation
    prefix: /{_locale}
    requirements:
        _locale: en|fr|de

na wszytskie ścieżki w systemie chcesz dołożyć locale to możesz to zrobić dodając element prefix w pliku powyżej. Jeśli jednak nie chcesz na wszytko, ponieważ API nie ma wersji językowych to możesz dodać do konkretnych akcji:

* @Route("/{_locale}/admin/product/new", name="admin_product_new")

Teraz wystarczy, że przeklikasz swoją aplikację i sprawdzisz czy na pewno wszystkie ścieżki działają i generują się poprawnie.

To by było na tyle. Jeśli masz jeszcze jakieś pytania, brakuje Ci informacji to pisz śmiało w komentarzu.

Podobne posty

Jestem programistką, która lubi mieć ręce pełne roboty. Do życia potrzebuje komputera z internetem i kubka gorącej kawy. Więcej na stronie o mnie.

Comments

  1. Dwukrotnie jest tekst:
    Default locale oznacza, że jeśli dany user nie będzie miał informacji o języku to zostanie mu pokazany angielski interfejs. Fallback określa, że jeśli user ma określony język ale nie może dla danego klucza znaleść tłumaczenia to pokaże angielską wersję.

    W Laraverze do tłumaczeń wykorzystywany jest trans, gdzie jako drugi parametr podajemy zmienne. Poważnie nie ma tego w czystym Symfony?
    W routerze jest możliwość pomijania {_locale} dla domyślnego języka?
    Np. domena.pl/en/contact, domena.pl/kontakt zamiast domena.pl/pl/kontakt?

    • Dzięki za zauważenie tego 🙂
      To jest tak na prawdę czyste Symfony jeśli mówimy o wersji od 4 w górę, a przykłady są właśnie z 4.2. Dawniej Symfony to był jeden core, który miał wszystko żeby zrobić pełną apkę m.in. translator więc było to w czystym Symfony. Od wersji 4 autorzy zmienili podejście i rozbili Symfony na komponenty. Przy bazowej instalacji jest minimum paczek. A reszta m.in. Translations trzeba doinstalować świadomie ale dalej jest to cześć czystego Symfony tylko nie instalującego się z automatu z projektem. Osobiście uważam, że bardzo dobre podejście, ponieważ nie obciąża się aplikacji komponentami, które nie są potrzebne. Nie każda aplikacja potrzebuje wielojęzyczności więc od SF4 instalujemy ją świadomie.
      Co do routingu, nie próbowałam tego nigdy, znalazłam takie rozwiązanie:
      contact:
      path: ‘/{_locale}{_S}contact’
      defaults: { _controller: ‘AppBundle:Example:index’ , _locale=”de” , _S: “/” }
      requirements:
      _S: “/?”
      _locale: ‘|de|en|fr’

      ale trzeba byłoby to wypróbować, czy na pewno spełnia w 100% Twoje zadanie.

  2. To właśnie mega plus Symfony 4, że ma minimum i musimy wykazać się minimalną wiedzą z zarządzania komponentami 🙂
    Ja również nie testowałem routingu, ale jeśli już wielojęzyczność to fajnie aby domena główna była językiem domyślnym, przynajmniej takie jest moje zdanie 🙂

ZOSTAW ODPOWIEDŹ

Please enter your comment!
Please enter your name here