Přejít k navigační liště

Zdroják » PHP » Symfony po krůčkách – Translation – překlady jednoduše

Symfony po krůčkách – Translation – překlady jednoduše

Články PHP

V tomto díle si ukážeme, jak použít pro překlady textů Symfony komponentu Translation. Ta umožňuje pracovat s překlady uloženými v různých formátech. To může usnadnit výměnu textů s překladatelem nebo externím API. Hodí se ale i k zobrazení různých textů v závislosti na počtu (1 jablko, 5 jablek).

Nálepky:

Příklad na začátek

Celý princip práce s překladačem je velmi jednoduchý. Pro každý jazyk, se kterým chceme pracovat, naplníme Translator našimi překlady – to je jen seznam klíčů a jejich hodnot. Klíč určuje řetězec, ke kterému hledáme překlad, hodnota jeho překlad. Odpovídající překlady pro různé jazyky jsou uloženy pod stejným klíčem. Pak už se jen Translatoru ptáme na překlady pro konkrétní klíč a jazyk.

Komponentu nainstalujeme jednoduše přes Composer:

composer require symfony/translation

A použijeme v jednoduchém skriptu:

<?php

use Symfony\Component\Translation\Loader\ArrayLoader;
use Symfony\Component\Translation\Translator;

require_once __DIR__. '/vendor/autoload.php';

$translator = new Translator('cs_CZ');

$translator->addLoader('array', new ArrayLoader());

$translator->addResource('array', [
 'Hello world!' => 'Ahoj světe!',
], 'cs_CZ');

echo $translator->trans('Hello world!');

Po spuštění se vypíše Ahoj světe!. Rozebereme si, co se v ukázce děje:

Translatoru určíme jazyk

Nejdříve vytváříme objekt třídy Translator. Tomu musíme předat v parametru locale určující výchozí jazyk, do kterého chceme překládat.

Doporučený formát locale je dvoupísmený kod jazyka podle ISO 639-1 následovaný podtržítkem a dvoupísmenným kódem státu podle ISO 3166-1.

Nastavíme loader

V dalším kroku se přidává instance třídy Loaderu. Loader umí načítat zdroje překladů v určitém formátu, tady pro "array".

Přidáme zdroje

Metoda addResource() přidává zdroj překladů. V tomto případě je to pole s jedním prvkem. Posledním argumentem říkáme, že texty jsou v češtině – v jednom zdroji jsou překlady vždy jen pro jeden jazyk.

Překládáme

Teď už můžeme na překladači zavolat metodu trans(), které předáme překládaný řetězec a dostaneme zpět jeho překlad. Pokud by se stalo, že by překlad neexistoval, vrátí se řetězec předávaný v argumentu.

Locale můžeme předat i metodě trans() jako čtvrtý argument a vynutit si tak překlad do jiného jazyka:

$translator->trans('Hello world!', [], null, 'en_EN');

Překládat původní text nebo zástupné klíče?

V úvodním příkladu vytváříme překladový slovník, kde klíčem překladu je přímo původní text v angličtině ('Hello world!') a ten se pak předává i metodě trans().

Druhá možnost je místo původního textu použít zástupný klíč, který by měl vyjadřovat obsah překladu. Zdrojový jazyk, v tomto případě angličtinu, je potřeba pak přidat jako další jazyk.

$translator->addResource('array', [
   'hello.world' => 'Ahoj světe!',
], 'cs_CZ');

$translator->addResource('array', [
   'hello.world' => 'Hello world!',
], 'en_EN');

echo $translator->trans('hello.world');

// “Ahoj světe!”

Použití klíčů má několik výhod:

  • Když se mírně změní překládaný zdrojový text, jeho klíč zůstane stejný i ve všech dalších překladech.
  • U dlouhých textů může být klíč také kratší.
  • Pokud budou překlady uložené v YAMLu, je možné je zanořovat. (Ukážeme si dále.)

Častěji bývá doporučován a používán právě způsob se zástupnými klíči. Volba je ale jen na vás.

Proměnné v překladech

Někdy je do překládaných textů potřeba vložit hodnotu dynamicky. K tomu účelu se do překládaného řetězce vkládají placeholdery a metodě trans() se předává pole náhrad:

$name = 'Zdroják';

echo $translator->trans(
   'Hello %name%',
   ['%name%' => $name]
);

// "Hello Zdroják"

Doporučuje se placeholder ohraničit znaky procenta, může mít ale libovolný formát.

Další způsoby načítání překladů

V příkladu jsme předali texty přímo v poli a k jejich zpracování použili ArrayLoader. Často jsou ale překlady připravovány profesionálními překladateli, kterým chceme dát seznam překladů v jiném formátu. Přímo s komponentou máte na výběr z dalších loaderů – CSV, Xliff, Php, Yaml, Po… Celý seznam naleznete v dokumentaci.

Oblíbeným formátem pro uchovávání překladů bývá YAML. Ten aktivujeme pomocí:

composer require symfony/yaml

YAML umožňuje zanořovat klíče:

#messages.cs.yml
greetings:
  hello: 'Ahoj'
  good:
    morning: 'Dobré ráno'
    afternoon: 'Dobré odpoledne'
    evening: 'Dobrý večer'

Použít překlady z YAML souboru pak můžeme takto:

use \Symfony\Component\Translation\Loader\YamlFileLoader;

$translator->addLoader('yaml', new YamlFileLoader());
$translator->addResource('yaml', 'messages.cs.yml', 'cs_CZ')

echo $translator->trans('greetings.good.morning')

// "Dobré ráno"

Domény – dělení do skupin

Překladové soubory jsou organizovány podle jednotlivých locales, které překládají. Další členění může být na “domény”. Můžeme tak oddělit překlady validačních hlášek, chyb nebo třeba administračního rozhraní do samostatných souborů.

Doména se předává jako 4. argument metodě addResource(). Výchozí hodnota je 'messages'.

$translator->addResource('yaml', 'messages.cs.yml', 'cs_CZ')
$translator->addResource('yaml', 'admin.cs.yml', 'cs_CZ', 'admin')

Překlad s použitím domény pak vypadá takto:

$translator->trans('Přidat nový záznam', [], 'admin');

Fallback překlady

Pokud není klíč překladu nalezen v seznamu překladů pro daný jazyk, lze určit pořadí dalších jazyků, které se mají brát jako náhrada.

Pokud chceme překlad pro 'cs_CZ' locale, hledá se v tomto pořadí:

  1. Překlad pro 'cs_CZ' locale
  2. Pokud není nalezen, hledá se v 'cs' locale
  3. Pokud není stále nalezen, hledá se ve fallback locale, které musí být explicitně nastaveno a může jich být více:
$translator->setFallbackLocales(['en']);

1 jablko, 5 jablek

V češtině jsou různé tvary podstatného jména pro “jeden”, “dva až čtyři” a “žádný nebo pět a více”. V angličtině jsou různé tvary jen dva – pro “jeden” a “žádný nebo více než jeden”. V jiných jazycích mohou být pravidla i složitější. Translation komponenta má podporu pro výběr vhodného tvaru ze zadaných možností.

V překladech je možné zadat více tvarů, oddělených znakem |:

‘You have 1 mail|You have %count% mails’

Místo metody trans() se pak používá metoda transChoice():

echo $translator->transChoice(
   ‘You have 1 mail|You have %count% mails’,
   10,
   ['%count%' => 10]
);

// “You have 10 mails”

Druhý argument v příkladu výše je počet, podle kterého se zvolí vhodný překlad. Počet se v překladu nahrazuje přes placeholder "%count%".

Každé locale má vlastní pravidla, podle kterých se vybírá vhodná forma. Pro češtinu může překladový řetězec vypadat takto:

echo $translator->transChoice(
   'Máte 1 zprávu|Máte %count% zprávy|Máte %count% zpráv',
   10,
   ['%count%' => 10],
   null,
   'cs'
);

// “Máte 10 zpráv”

Profi intervaly

Někdy můžeme chtít vybrat různý text pro konkrétní počet, například pro “0”. Pro takové případy je možné uvést přímo intervaly čísel, pro které se má zvolit která zpráva:

'{0} Nemáte žádnou zprávu|{1} Máte jednu zprávu|]1,19] Máte několik zpráv|[20,Inf[ Máte moc zpráv'

Přístupy lze i kombinovat. V případě, že pro počet nelze vybrat konkrétní interval, berou se standardní pravidla po odstranění intervalových:

'{0} Nemáte žádnou zprávu|[1000,Inf[ Máte hodně moc zpráv|Máte 1 zprávu|Máte %count% zprávy|Máte %count% zpráv'

Interval může být konečný seznam čísel:

{1,2,3,4}

Nebo interval mezi dvěma čísly:

[1, +Inf[
]-1,2[

Jako v matematice může být interval zleva uzavřený [ nebo otevřený ], a zprava uzavřený ] nebo otevřený [. Kromě čísel jde použít i -Inf a +Inf pro nekonečno.

Zase o krok dále

Dnes jsme si ukázali komponetu, která nám pomůže:

  • překládát pro různé jazyky
  • načítat různé typy překladových souborů
  • počítá s množnými čísly
  • jak využít intervaly pro hlášku na míru

Kam jít dál?

Víc si o Translation komponentě můžete přečist v oficiální dokumentaci.

Do Nette existuje integrace komponenty v Kdyby/Translation od Filipa Procházky. Ve full-stack Symfony frameworku jsou překlady integrované a rozšířené ještě o další nástroje. To si ukážeme v některém z dalších dílů.

Už v Symfony děláš léta a chceš posdílet zkušenosti?

Nebo tě Symfony zatím jen láká a rád by se o něm dozvěděl více? Přijd si pokecat na pravidelné měsíční srazy v Praze, Brně a Ostravě.

Komentáře

Subscribe
Upozornit na
guest
2 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Luděk Benedík

Pěkný článek. Jen bych pro fallbacks doplnil:

V kontrukci $translator = new Translator('cs_CZ'); vidíme hodnotu cs_CZ, kterou můžeme získávat např. z requestu. V takovém případě bude translátoru často namísto hodnoty cs_CZ předána hodnota cs. A teď důležitá věc! Postupné hledání jazyků cs_CZ a následně cs platí pouze pro překládáný jazyk, nikoliv pro jazyk registrovaných překladů. Jinak řečeno, registrované překlady pro jazyk cs_CZ jsou pouze pro jazyk cs_CZ a nikoliv pro jazyk cs. Nejlépe to bude vidět na příkladě.

$translator = new Translator('en'); // en, en_GB, en_US
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array(
    'metro' => 'Underground',
), 'en_GB');
$translator->addResource('array', array(
    'metro' => 'Subway',
), 'en_US');

echo $translator->trans('metro', [], null, 'en');    // metro
echo $translator->trans('metro', [], null, 'en_GB'); // Underground
echo $translator->trans('metro', [], null, 'en_US'); // Subway

Jak je na příkladě vidět, pro jazyk en překlad neexistuje.
Proto je vhodné si pro každý registrovaný jazyk určit výchozí „mutaci“.
Ukážeme si to opět nejlépe na příkladě.

$translator = new Translator('en'); // en, en_GB, en_US
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array(
    'metro' => 'Underground',
), 'en');
$translator->addResource('array', array(
    'metro' => 'Subway',
), 'en_US');

echo $translator->trans('metro', [], null, 'en');    // Underground
echo $translator->trans('metro', [], null, 'en_GB'); // Underground
echo $translator->trans('metro', [], null, 'en_US'); // Subway
Milan Krupa

Pěkný článek, díky za něj. Jen mi tam trošku chybí dvě věci:

  1. možnost automatické extrakce řetězců pro překlad (app/console translation:update) a při té příležitosti zmínit jms/translation-bundle, který umí extrahovat nejen ze šablon, ale i z controllerů a formulářů.
  2. Máš nějaký osvědčený způsob, jak používat proměnné v překladu při používání zástupných klíčů? Jde o to, aby překladatel hned viděl, že v dané větě může použít proměnné a jak se jmenují.

Např.
Dnes je %date% a svátek má %name%“ => today.is.DATE.nameday.has.NAME

případně pokud jsou klíče založené na struktuře webu:
welcome.nameday.info.DATE-NAME (na welcome stránce přelož informaci o svátku; v překladu můžete použít proměnné %date% a %name%)

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.