Integrace Symfony translation do Nette – Kdyby/Translation
- Tomáš Votruba
- Moderator | 1114
// edited by @Filip Procházka:
Zdravím,
Kdyby/Translation je doplněk
pro Nette, který do něj integruje Symfony/Translation, nástroj který umí používat formáty
slovníků od gettextového po/mo
až po Neon (vlastní
implementace)
Samozřejmostí je podpora stable i dev Nette a také nejnovější
verze translatoru (tedy 2.3@stable
).
Rozšíření je klasicky na Githubu k vašim službám včetně dokumentace, nejlépe ho instalujte pomocí composeru. Chyby nebo nápady na vylepšení hlaste prosím na githubu
Instalace
Pokud chcete používat vývojové Nette, tak do composer.json
dáme následující
"require": {
"nette/nette": "@dev",
"kdyby/translation": "@dev"
}
Pokud stable Nette
"require": {
"nette/nette": "~2.0",
"kdyby/translation": "~0.10"
}
(Pozn.: řada 0.*
je pro ~2.0@stable
Nette, řada
1.*
je pro ~2.1@dev
Nette)
Na co se těšit:
- Pracuju na kulervoucím extractoru, který se nezalekne žádných stringů :)
Disclaimer: Kdyby/Translation
je
plnohodnotný nástroj na multijazyčnost do Nette, ovšem
neobsahuje proklínaný panel na překlad (a nikdy nebude, protože je to
prasárna) a není to ani fork starého gettext translatoru.
Je to úplně nový nástroj, „pouhá“ integrace
Symfony/Translation
do Nette s cukrlátky.
Pokud jste přišli z gettext translatoru, přechod by vám mohla usnadnit jednoduchá integrace pomocí traity.
Editoval Tomáš Votruba (14. 11. 2013 15:42)
- Filip Procházka
- Moderator | 4668
@Schmutzka: Díky za založení stránky :)
@bazo: Protože nemám rád překládání přes
funkce _()
(kvůli nezbytnosti globálního stavu) a navíc plánuju něco
lepšího :)
- Tomáš Votruba
- Moderator | 1114
@Filip Procházka: Rádo se stalo :). Byla by škoda tu něco takového nemít. Chtěl jsem napsat uprav dle potřeby, ale taky už se stalo. Paráda.
- Filip Procházka
- Moderator | 4668
Bude to trochu mocnejší než jenom extrakce stringů :) Každopádně, co to popisuješ bude umět taky :)
- akadlec
- Člen | 1326
Jeden možná hloupý dotaz. Na začátku vývoje sem si nahodil překladač s gettextem co byl v addonech. Funguje je mi fajn, resp zatím neřeším překlady ale jen mi to extrahuje texty co to má přeložit a hází do laděnky. Celá app je napsána v EN a překlady řeším takto:
$this->translator->translate('Warning! Something went wrong.');
či v šabloně:
<p>{_'Hello world'}</p>
Takže jako překladovou frázi mám celý text v EN abych se vyhnul problému kdy by se nenačetl konkrétní překlad pro zvolený jazyk tak je dle mého lepší to zobrazit v EN než nějaký string typu: message.home.hello
Ale jak jsem zjistil tak gettext plugin už asi umřel a dále se nebude vyvíjet a tak přemýšlím zda u něj zůstat nebo přejít na Kdyby. Jenže po zkouknutí kdyby jsem zjistil že to překlady řeší těmi řetězci.
Co je tedy lepší řešení?
- Tomáš Votruba
- Moderator | 1114
@akadlec: Je to jen otázka zvyku – kvůli němu jsem zvažoval přechod pár týdnů. Výhodu v zástupném stringu vidím zejména pro překlady (překládající), nikoliv pro programovací návyky:
- máš-li v šabloně string „Helo boys“, který přeložíš a pak opravíš na „Hello boys“, s gettextem můžeš překládat znovu
- pokud potřebuješ někým přeložit aplikaci, vezmeš
.neon
(případně.txt
), pošleš je překladateli a on ti vrátí přeloženou verzi × s gettextem musíš posílat.mo
/.po
soubory, které překladatel znát nemusí - potřebuješ-li 3 a více jazyků, je vhodnější mít různé jazykové verze k překladu (pl → hu, en → cs, de → ru) a ne žádnou základní × s gettextem je vždy jedna v šabloně a od té se odvíjí každá další (en → cs, en → pl, en → hu…)
Pokud máš aplikaci pouze v angličtině, nemá smysl přidávat překladač.
- akadlec
- Člen | 1326
@Tomáš Votruba: Jasný je to zvyk.
- Jo s tímhle jsem počítal, že pokud bych udělal změnu řetězce v šabloně tak bych to musel přegenerovat ve všech jazycích
- Tohle omezení je mě jasné, tady jsem do budoucna počítal udělat překladateli rozhraní kde to uvidí v EN a bude dělat překlady přes rozhraní
- No tady by asi byl základní překlad CS – zbytek, s tím že EN a CS zajištujě programátor s nutnou korekcí.
Appka je primárně EN a pak podle potřeby další jazyky CS, SK atd.
Jen mě teda příjde příjemnější číst šablony s reálným textem než s řetězci ;)
- Filip Procházka
- Moderator | 4668
Translator by měl jít používat i přímo, bez domén (těch message ids s tečkami).
- Jan Tvrdík
- Nette guru | 2595
@Filip Procházka: Dovolím si připomenout jeden zapomenutý feature request.
- Eda
- Backer | 220
Zdarec.
Pořád se mi nějak nedaří tohle rozšíření rozjet tak, jak bych si představoval.
Co jsem tedy udělal.
- nainstalováno composerem, @dev verze
- do config.neon:
...
extensions:
translation: Kdyby\Translation\DI\TranslationExtension
translation:
default: sk
fallback: [sk_SK, sk]
- V BasePresenteru persistentní $locale, v routě nastavena výchozí hodnota na sk.
- app/lang/messages.sk_SK.neon:
carRegisterTitleMaker:
manufacturer: výrobca
- Ve službě, kde mám injectnutý translator pak zavolám:
$this->translator->translate('carRegisterTitleMaker.manufacturer')
Místo překladu se mi ale vypíše: „carRegisterTitleMaker.manufacturer“ a v panelu svítí chybějící překlad (jazyk ‚sk‘, to je rozpoznáno správně). Kde může být chyba?
Je zajímavé, že když změním v konfigu výchozí jazyk za češtinu:
translation:
default: cs
fallback: [cs_CZ, cs]
A přejmenuju soubor na messages.cs_CZ.neon
, tak se najednou
vše načte správně a skutečně se vypíše překlad (nezobrazí se ovšem
v panelu chybějící překlad, což je taky trošku problém).
Ještě mám dotaz: od přečtení návodu jsem docela zmaten, jakých hodnot může nabývat locale/jazyk a z toho, jak se přesně chová fallback nastavovaný v konfigu.
Takže:
- Proč se mají soubory s překlady pojmenovávat ve stylu
messages.cs_CZ.neon
a nikolivmessages.cs.neon
, když perzistentní proměnná $locale má vždy jen dvoupísmennou hodnotu? - Jak rozšíření zjistí, zda má použít
messages.en_GB.neon
, nebomessages.en_US.neon
, když má $locale hodnotu „en“? - „Fallback“ chápu intuitivně tak, že vyjmenuju pořadí jazykových
mutací, jejichž překlad se má použít, pokud nebude k dispozici překlad
z nastaveného jazyka. Tedy třeba v případě webu sk/cz/en bych mohl
nastavit
fallback: [cs, sk, en]
. Proč se ale v příkladu nastavení objevujefallback: [cs_CZ, cs]
?
Díky za případné odpovědi :-)
Editoval Eda (17. 11. 2013 20:06)
- Filip Procházka
- Moderator | 4668
@Eda: Prozatím ti řeknu, že musíš doplnit
whitelist o sk
a já dokumentaci aby to bylo na očích :)
- MartinitCZ
- Člen | 580
@**Filip Procházka**: Dalo by se nějak udělat, že jazyk nebude v routě? Rád bych měl překlady, ale nechci mít v url /en nebo /cz…
- Filip Procházka
- Moderator | 4668
@martinit jasně, napiš si vlastní resolver a do routy to vůbec nedávej :) Ale budeš to muset mít někde v session (takovej by mohl být přímo v mé extension abys ho jen použil, že?).
- MartinitCZ
- Člen | 580
@**Filip Procházka**: Určitě by to bylo super :) A jak to vypadá s překladem fomulářů a jejich error messages? Pokud vím, tak aktuálně to zvládá jen šablony, nebo se pletu?
- sKopheK
- Člen | 207
Konečně jsem pospojoval všechny dílky puzzle a rozjel jsem ten současný extractor. Pokud jste také Nette/Kdyby konzolí nepolíbení, přidám pár hintů:
- informace o Kdyby/Console: https://forum.nette.org/…dyby-console
- stáhnout do knihoven k Nette / do souboru
composer.json
přidat + updatnout
"require": {
...
"kdyby/translation": "@dev",
"kdyby/console": "2.0.*@dev"
},
- ideálně do
config.local.neon
services:
extractCommand:
class: Kdyby\Translation\Console\ExtractCommand
tags: [kdyby.console.command]
extensions:
console: Kdyby\Console\DI\ConsoleExtension
console:
url: http://www.kdyby.org
- pustit PHP CLI z kořenového adresáře aplikace
php www/index.php kdyby:translation-extract -f neon
Namísto neon můžete dosadit libovolný formát a stejně tak můžete upřesnit další parametry.
- MartinitCZ
- Člen | 580
@**sKopheK**: Bohužel to nefunguje. Napíše mi to Catalogue was written to …, ale nic tam není.
- Filip Procházka
- Moderator | 4668
@martinit: formuláře i errory to překládat umí, ale protože nette to má blbě udělané, tak to neumí překládat dodatečné parametry. Ale to je problém Nette, nikoliv translatoru. Něco s tím ještě zkusím vymyslet :)
@sKopheK: když zaregistruješ
kdyby/console
před
translation tak se ten command zaregistruje sám a bude ti stačit
"require": {
"nette/nette": "@dev",
"kdyby/translation": "@dev",
"kdyby/console": "@dev",
...
},
extensions:
console: Kdyby\Console\DI\ConsoleExtension
translation: Kdyby\Translation\DI\TranslationExtension
Dyštak jsou tam i parametry na jazyk katalogu a složku kam má zapisovat
$ php www/index.php help kdyby:translation-extract
Ale bohužel, zatím umí jenom latte
, php soubory teprve budou.
- sKopheK
- Člen | 207
@Filip Procházka: Konzoli pro produkční prostředí nepotřebuji, tak ji mám jen v lokálním configu. Nějaký primitivní extractor z PHP souborů jsem si vyrobil, něco málo času to snad ušetří. Jsem zvědavý jak moc robustní bude oficiální extractor z kódu PHP.
@martinit: Zkontroloval jsem, zda jsem na nějaký krok nezapomněl, ale dělal jsem to přesně tak, jak jsem popsal výše. Používám Nette 2.1dev; v dřívější verzi je postup registrace služeb trošku jinačí – zmiňuje to manuál k Kdyby/Console. Při přidání svého extractoru jsem musel samozřejmě smazat cache od celé Nette aplikace, možná to pomůže i tobě.
- sKopheK
- Člen | 207
martinit napsal(a):
@**Filip Procházka**: Dalo by se nějak udělat, že jazyk nebude v routě? Rád bych měl překlady, ale nechci mít v url /en nebo /cz…
Když si před routy dáš např. [<locale=cs [a-z]{2}>/]
(v kombinaci s @persistent public $locale
v presenteru), tak
budeš mít pro translator nastavenou češtinu a v URL se to neobjeví.
- MartinitCZ
- Člen | 580
@**sKopheK**: To vše jsem zkoušel, ale nepreparuje to :/
To ohledně url vím, ale funguje to jen pro defaultní jazyk.
- Filip Procházka
- Moderator | 4668
sKopheK napsal(a):
@Filip Procházka: Konzoli pro produkční prostředí nepotřebuji, tak ji mám jen v lokálním configu.
To že máš registrovaný extension (a ještě přes config, což to ještě více zefektivňuje) nemáš šanci na produkci naměřit.
- sKopheK
- Člen | 207
Narazil jsem na drobnou chybku:
setTranslator(NULL)
u formulářového prvku se nezohlední a
snaží se text chyby u pravidla addRule()
přeložit –
v Nette panelu se pak zobrazuje jako nepřeložený text. U textu labelu
problém není.
$form->addSelect('id_game', $this->translator->translate("event.phase.form.game"), $games)
->setTranslator(NULL)
->addRule(Form::RANGE, $this->translator->translate("event.phase.form.gameError"), array(1, NULL))
->getControlPrototype()
->class('form-control');
- Filip Procházka
- Moderator | 4668
S tím asi moc neudělám, ale můžeš zkusit poslat pull request do Nette :)
- Felix
- Nette Core | 1196
Upozornuju, ze jsem jeste nepouzil, jenom premyslim jak bych to pouzival.
Jak se vyhnout dopredu tomu, ze nektere vety budou na vice strankach a ja na to narazim az potom co si budu projizdet neon, treba hlaska „Odeslani se nezdarilo ..“ apod.
To potom, abych si dopredu zalozil namespace common.messages.error napriklad – ale nevim jestli budu dopredu znat vsechny.
Kdyz si predstavim, ze mam velkou aplikaci s hodne textama a mailama. Tak si nejsem jistej, jestli obsahnu vsechny zastupny texty. Napriklad mails.newuser.paragraph1 .. paragraph2.. paragraph15.
Takhle mi zatim v sablone staci jenom texty hloupe obalovat do {_".."} a vysledek uvidim az po projeti sablon.
Urcite to ma sve pro i proti, spis si rikam kdy dostanu ten impuls abych presel :-)
- Filip Procházka
- Moderator | 4668
Naopak bych řekl, že duplicity vůbec nevadí, dokonce jsou dobře. Občas chceš použít stejný výraz ale v různých kontextech a v jiném jazyku proto třeba na různých místech bude vhodnější výraz, nebo fráze.
- MartinitCZ
- Člen | 580
@**Filip Procházka**: Koukal jsem, že už jsi přidal session, ale jak to funguje? Zkoušel jsem to dát podle testů dohromady a nefunguje to:
bd($this->translator->getLocale()); // cs
bd($this->translatorSession->resolve($this->translator)); // NULL
$this->translatorSession->setLocale('en');
bd($this->translator->getLocale()); // cs
bd($this->translatorSession->resolve($this->translator)); // NULL
- Filip Procházka
- Moderator | 4668
Tak tohle nevím kde jsi našel, ale rozhodně to nedáváš dohromady podle testů ;) Metodu resolve si totiž volá translator sám (na to v testech nikde nesahám a ty bys taky neměl).
// začne request, vezme se locale z hlaviček prohlížeče
bd($this->translator->getLocale()); // cs
// v signálu presenteru (protože uživatel klik na změnu jazyka) zavoláš
$this->translatorSession->setLocale('en');
// a tím se locale uloží, ovšem nezmění se locale translatoru
// pokud chceš to nové locale použít ještě v tomto requestu tak musíš to současné resetovat
$this->translator->setLocale(NULL);
// teď už to vrátí locale z SessionResolveru
bd($this->translator->getLocale()); // en
// další request už bude automaticky obsahovat to locale které jsi nastavil
- MartinitCZ
- Člen | 580
@**Filip Procházka**: Díky za pomoc, ale stejnak je to divné. Nikdy se to nezmění na angličtinu.
public function handleLag()
{
$this->translatorSession->setLocale('en');
//$this->translator->setLocale(NULL);
$this->redirect('this');
}
public function handleLag2()
{
$this->translatorSession->setLocale('cs');
//$this->translator->setLocale(NULL);
$this->redirect('this');
}
Vždy je jazyk v cs. Ve fallback (které mi přijde jako zbytečne) mám fallback: [cs_CZ, cs, en, en_US, en_GB]
- Filip Procházka
- Moderator | 4668
A co máš v translatorSession
, předpokládám správně že
SessionResolver
? Možná bych to spíš udělal jako
public function handleChangeLocale($locale)
{
$this->translatorSession->setLocale($locale);
$this->redirect('this');
}
<a n:href="changeLocale!, cs">cs</a>
<a n:href="changeLocale!, en">en</a>
Každopádně, přesně tohle by mělo fungovat. Pořádně to otestuju a dám vědět :)
- MartinitCZ
- Člen | 580
@**Filip Procházka**: Přesně tak to mám, použil jsem i tvůj příklad a nepomůže to. Budu rád, když na to koukneš.
- Filip Procházka
- Moderator | 4668
Vytvořím projekt
$ composer create-project hosiplan/project translator-sessioned
$ cd translator-sessioned/
$ composer require kdyby/translation:@dev
přepíšu app/templates/Homepage/default.latte
{_front.homepage.hello}
<a n:href="changeLocale!, cs">cs</a>
<a n:href="changeLocale!, en">en</a>
přepíšu app/presenters/HomepagePresenter.php
class HomepagePresenter extends BasePresenter
{
/**
* @var \Kdyby\Translation\Translator
* @autowire
*/
protected $translator;
/**
* @var \Kdyby\Translation\LocaleResolver\SessionResolver
* @autowire
*/
protected $translatorSession;
public function handleChangeLocale($locale)
{
$this->translatorSession->setLocale($locale);
$this->redirect('this');
}
protected function createTemplate($class = NULL)
{
/** @var Nette\Templating\FileTemplate|\stdClass $template */
$template = parent::createTemplate($class);
$template->registerHelperLoader([$this->translator->createTemplateHelpers(), 'loader']);
return $template;
}
}
A funguju…
První načtení:
Kliknu na en:
Kliknu na cs a zase vidím to první.
- MartinitCZ
- Člen | 580
@**Filip Procházka**: Hmmm díky. Jdu hledat kde je problém, ale … Když kliknu na en, tak debugbar previous ukazuje správně en, ale ten akutuální zase ukazuje cs.
- NiNu
- Člen | 31
Zdravím,
skúšam túto komponentu a podľa návodu sa mi to podarilo bez problémov
rozbehať, aspoň čo sa týka prekladu formulárov a template-u. Chcel by som
sa ale spýtať či sa cez tento translator dajú prekladať aj texty ťahané
z DB, napr. položky v menu a ich stránky, alebo články z DB či novinky,
alebo sa to rieši nejak inak?
Ďakujem
- craz
- Člen | 34
@NiNu: odporúčam sa pozrieť tu, sú tam pekne rozpísané najpoužívanejšie prístupy k prekladom v databáze.
- NiNu
- Člen | 31
@craz: Diky moc, presne takéto niečo som hľadal. Bez toho aby som toto videl ma intuitívne napadla práve tá 4 možnosť (4. Additional Translation Table Approach), len som si nevedel celkom predstaviť ako na to. Teraz mi je to už celkom jasné a pustím sa do toho.
Ešte by ma zaujímal extractor, videl som, že kdyby/translation má nejaký, len by som chcel vidieť nejaký príklad ako to použiť.
- NiNu
- Člen | 31
akadlec napsal(a):
Tak v okamžiku kdy ukládáš překlady do DB snad extraktor a vubec translation extension nepotřebuješ ne? Překlady si řídíš sám nějakým edit formem konkrétní položky
ale hej, máš pravdu, použiť sa to nechystám, len ma to zaujíma, že ako to funguje po praktickej stránke, nejaký príklad keby som to do budúcna potreboval …
- NiNu
- Člen | 31
akadlec napsal(a):
Tak jednoduše třeba v editačním formu budeš mít 3 políčka pro „name“ v jednotlivých jazycích co chceš používat, třeba EN, CZ, SK a při ukládání si to do těch tabulek rozhodíš.
Ale nee, je mi jasné, že si rozšírim formuláre o príslušné preklady, ja som mal na mysli skôr ten extraktor, že ako funguje (nejaký malý príklad) …
- MartinitCZ
- Člen | 580
@**enumag**: Po dleší době bych se k tomu rád vrátil. Jak toto zjistim? Do session totiž v tomto projektu nikde nic neukládám.
Editoval martinit (10. 12. 2013 20:38)