Integrace Symfony translation do Nette – Kdyby/Translation

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Tomáš Votruba
Moderator | 1110
+
0
-

// 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:


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)

bazo
Člen | 619
+
0
-

skoda, ze extrahuje message len zo sablon

Filip Procházka
Moderator | 4668
+
0
-

@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 | 1110
+
0
-

@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.

bazo
Člen | 619
+
0
-

@Filip: neviem co myslis funkciou _(), extractor co ja pouzivam extrahuje message z formularov, datagridov, flash messagov atd. inak standardne pouzivam $translator->translate(), odkial to tiez vytiahne.

Cize spravis extractory antique tym, ze spravis novy extractor? :D

Filip Procházka
Moderator | 4668
+
0
-

Bude to trochu mocnejší než jenom extrakce stringů :) Každopádně, co to popisuješ bude umět taky :)

Ivorius
Nette Blogger | 119
+
0
-

Možná by nebylo špatné popsat přechod z getexttranslatoru? Respektive jak do toho zakomponovat již vytvořené překlady.

akadlec
Člen | 1323
+
0
-

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 | 1110
+
0
-

@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 | 1323
+
0
-

@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
+
0
-

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 | 2572
+
0
-

@Filip Procházka: Dovolím si připomenout jeden zapomenutý feature request.

Filip Procházka
Moderator | 4668
+
0
-

@Jan Tvrdík: díky za připomenutí, přidávám na hromádku :)

Eda
Backer | 218
+
0
-

Zdarec.

Pořád se mi nějak nedaří tohle rozšíření rozjet tak, jak bych si představoval.

Co jsem tedy udělal.

  1. nainstalováno composerem, @dev verze
  2. do config.neon:
...
extensions:
	translation: Kdyby\Translation\DI\TranslationExtension

translation:
	default: sk
	fallback: [sk_SK, sk]
  1. V BasePresenteru persistentní $locale, v routě nastavena výchozí hodnota na sk.
  2. app/lang/messages.sk_SK.neon:
carRegisterTitleMaker:
	manufacturer: výrobca
  1. 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:

  1. Proč se mají soubory s překlady pojmenovávat ve stylu messages.cs_CZ.neon a nikoliv messages.cs.neon, když perzistentní proměnná $locale má vždy jen dvoupísmennou hodnotu?
  2. Jak rozšíření zjistí, zda má použít messages.en_GB.neon, nebo messages.en_US.neon, když má $locale hodnotu „en“?
  3. „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í objevuje fallback: [cs_CZ, cs]?

Díky za případné odpovědi :-)

Editoval Eda (17. 11. 2013 20:06)

Filip Procházka
Moderator | 4668
+
0
-

@Eda: Prozatím ti řeknu, že musíš doplnit whitelist o sk a já dokumentaci aby to bylo na očích :)

MartinitCZ
Člen | 580
+
0
-

@**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
+
0
-

@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
+
0
-

@**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
+
0
-

Můžu se informovat, zda se vyplatí pár dní počkat na dokončení toho kulerváče, nebo si mám vytahat všechny překládané texty sám?

sKopheK
Člen | 207
+
0
-

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ů:

  1. informace o Kdyby/Console: https://forum.nette.org/…dyby-console
  2. stáhnout do knihoven k Nette / do souboru composer.json přidat + updatnout
"require": {
    ...
    "kdyby/translation": "@dev",
    "kdyby/console": "2.0.*@dev"
},
  1. 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
  1. 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
+
0
-

@**sKopheK**: Bohužel to nefunguje. Napíše mi to Catalogue was written to …, ale nic tam není.

Filip Procházka
Moderator | 4668
+
0
-

@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
+
0
-

@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
+
0
-

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
+
0
-

@**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
+
0
-

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
+
0
-

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
+
0
-

S tím asi moc neudělám, ale můžeš zkusit poslat pull request do Nette :)

Felix
Nette Core | 1137
+
0
-

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 :-)

sKopheK
Člen | 207
+
0
-

@Felix: Když použiješ nějaký extractor, tak dostaneš řetězce, které překládáš, a jak je po jednom nahrazuješ v kódu, tak hledáš, ve kterých souborech je ten řetězec použitý. Podle toho získáš představu, k čemu všemu se ten řetězec váže a můžeš si ho zařadit.

Filip Procházka
Moderator | 4668
+
0
-

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
+
0
-

@**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
+
0
-

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
+
0
-

@**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
+
0
-

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
+
0
-

@**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
+
+1
-

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
+
0
-

@**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.

enumag
Člen | 2118
+
0
-

@martinit: Zřejmě nastal nějaký problém při ukládání session. To se stává typicky v případě kdy do session dáváš objekty, které nelze serializovat.

NiNu
Člen | 31
+
0
-

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

akadlec
Člen | 1323
+
0
-

@NiNu: tak můžeš když si to tím tranlsatorem proženeš ale IMHO je to blbost. Když už to máš v DB tak se předpokládá že se jedná o dynamický text takže bych to do db ukladat v jazykovych verzich jaké potřebuješ.

NiNu
Člen | 31
+
0
-

@akadlec: ok, ďakujem a aký je teda najlepší spôsob ukladať tú jazykovú verziu do db? Ako sa to bežne robí? Vytvoriť novú tabuľku s jazykovou mutáciou? Alebo nejak inak?

akadlec
Člen | 1323
+
0
-

@NiNu: ano třeba takto. Vytvořís si lokalizační tabulku kde to budeš vázat na konkrétní záznamy a jazyk.

craz
Člen | 34
+
0
-

@NiNu: odporúčam sa pozrieť tu, sú tam pekne rozpísané najpoužívanejšie prístupy k prekladom v databáze.

NiNu
Člen | 31
+
0
-

@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ť.

akadlec
Člen | 1323
+
0
-

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

NiNu
Člen | 31
+
0
-

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 …

akadlec
Člen | 1323
+
0
-

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íš.

NiNu
Člen | 31
+
0
-

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
+
0
-

@**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)