Vícejazyčné routování – Pro každý jazyk jiná doména, překlady rout, modul pro jeden jazyk
- Laxren
- Člen | 23
Zdravím,
Mám 3 takové otázky na vícejazyčné routování.
Používám Kdyby/Translation.
Na fórech a všude jinde jsem našel nějaké příklady, jak to udělat. Taky jsem k tomu dospěl, že to funguje, ale takový způsob se mi zdá hodně nepraktický, tak jsem se chtěl zeptat, jak se to dá napsat více korektně.
1) Pro každý jazyk jiná doména.. abcd.com ⇒ en .. tralala.cz
⇒ cs (a třeba abcd.de ⇒ de)
Nějaké řešení u kterého bych nemusel pořád specifikovat jaká doména
se má použít, protože těch rout ve skutečnosti je mnohem více a psát to
pro každou mi přijde zbytečné. Možná to udělat nějak přes persistentní
parametr, ale vůbec nevím.
Můj dosavadní kód + kód z bodu 2.
<?php
$router[] = $front = new RouteList('Front');
$front[] = new Route('//<locale>/<presenter>/<action>', array(
'locale' => [
Route::FILTER_TABLE => [
'abcd.com' => 'en',
'tralala.cz' => 'cs'
]
],
'presenter' => 'Homepage',
'action' => 'default'
));
?>
2) Nejlepší řešení pro překlady rout, pro 2 jazyky (v budoucnu pro více jazyků)
Můj dosavadní kód:
<?php
$router[] = $front = new RouteList('Front');
$front[] = new Route('//abcd.com/news', array(
'locale' => 'en',
'presenter' => 'News',
'action' => 'default'
));
$front[] = new Route('//abcd.com/news/<newsUrl>', array(
'locale' => 'en',
'presenter' => 'News',
'action' => 'detail'
));
$front[] = new Route('//tralala.cz/novinky', array(
'locale' => 'cs',
'presenter' => 'News',
'action' => 'default'
));
$front[] = new Route('//tralala.cz/novinky/<newsUrl>', array(
'locale' => 'cs',
'presenter' => 'News',
'action' => 'detail'
));
?>
3) Modul který bude jen pro jeden jazyk, jednu doménu
Zde by bylo dobré, aby věděl, že když to je Back modul, tak se to má
automaticky použít jen pro českou verzi.
Můj dosavadní kód:
<?php
$router[] = $back = new RouteList('Back');
$back[] = new Route('//tralala.cz/back/detail', array(
'locale' => 'cs',
'presenter' => 'Product',
'action' => 'detail'
));
$back[] = new Route('//tralala.cz/back<presenter>/<action>', array(
'locale' => 'cs',
'presenter' => 'Homepage',
'action' => 'default'
));
?>
- David Matějka
- Moderator | 6445
to 1) je ok, persistentni parametr je dobry pristup, staci pridat do nejakeho spolecneho base presenteru
/** @persistent */
public $locale;
a nemusis se o nic starat.
pokud bys vsak potreboval mit ty domeny dynamicky (treba z db), tak uz to stacit nebude. a ty preklady new/novinky nejsou taky stastne vyreseny.
na vyreseni obou problemu (pokud si nechces napsat zcela vlastni router)
pomuzou globalni
filtry. v nich se dostanes ke vsem parametrum (tedy domena, respektive
locale, nazev presenteru, akce..) a muzes je dle potreby prekledat prekladat
v zavislosti na tom locale. navic tim muzes vyresit i problem 3 a to tak, ze
z toho globalniho filtru vratis null
, pro ty domeny, ktere nemaji
povoleny ten modul.
- Laxren
- Člen | 23
Děkuju za odpověď.
Persistentní parametr $locale
mám, ale stejně budu muset vždy
použít filter_table
, aby věděl jaká doména se má použít,
ne? (to ale asi vyřeší ty globální filtry)
Zeptám se jinak..
Když nastavím globální filter, tak se aplikuje i pro všechny ostatní
routy?
Třeba, když to nastavím takto..
$router[] = $front = new RouteList('Front');
$front[] = new Route('//<locale>/<presenter>/<action>', array(
'locale' => null, //locale se pak asi nastaví ve filtrech
'presenter' => 'Homepage',
'action' => 'default',
null => [
Route::FILTER_IN => function (array $params) {
// ...
return $params;
},
Route::FILTER_OUT => function (array $params) {
// ...
return $params;
},
],
));
tak se to aplikuje i pro ostatní (například pro všechny v rámci
jednoho modulu) bez toho, aniž bych to musel psát znova?
Je to trochu blbá otázka, když se to jmenuje „globální“, ale radši se
ujistím, jestli to tak opravdu funguje..
Nebo bych to musel napsat třeba do funkce kterou bych pak vždycky pro každou
routu volal?
Další věc jak správně použít Route::FILTER_IN
a
Route::FILTER_OUT
? Co nacpat do IN a co do OUT? Někde tady na
fóru jsem to viděl trochu vysvětlené, jen někde v komentech, ale nemůžu
to najít a v dokumentaci to není přímo vysvětlené co to přesně dělá,
jestli se nepletu.
Nějaké vysvětlení pro nechápavce by nebylo na škodu :/
PS: Jde mi hlavně o to, abych nemusel pořád dokola specifikovat jaká doména a kdy se má použít
- David Matějka
- Moderator | 6445
je to globalni v tom smyslu, ze se to aplikuje na vsechny parametry, ale i tak to musis uvest u kazde routy. takze nejlepsi bude asi to, ze si udelas nejakou metodu createRoute, ktera tam automaticky pripoji ten globalni filtr.
v FILTER_IN dostanes na vstupu parametry od uzivatele (respektive z masky routy) a prevedes je na ty parametry, ktere ocekava presenter. takze ti tam prijde domena, prevedes to na locale. prijde ti tam retezec „novinky“ a ty ho dle locale prevedes na prislusny nazev presenteru. v FILTER_OUT udelas presne opacnou transformaci
- Laxren
- Člen | 23
Děkuju za vysvětlení.
S těmi globálnimi filtry to dopadlo takhle..
Ještě se neudělala ta metoda na automatické připojování globálního
filtru, pro ty domény, aby se pořád nemusel zápis opakovat. Nevím si s ní
rady.
Popostrčil by mě někdo prosím?
Automatické připojování jsem zkoušel takto. Myslelo se to s tímhle
způsobem, nebo jinak?
Akorát přesně takhle to nešlo, kvůli statické createRouter.
public static function createRouter() {
$front[] = new Route('//<locale>/<presenter>/<action>', array(
'presenter' => 'Homepage',
'action' => 'default',
null => [
Route::FILTER_IN => 'filterInFunc',
Route::FILTER_OUT => 'filterOutFunc',
],
));
}
public function filterInFunc(array $params){
if ($params['locale'] == 'tralala.cz'){
$params['locale'] = 'cs';
}
if($params['locale'] == 'abcd.com'){
$params['locale'] = 'en';
}
return $params;
}
Překlad
Je už takhle řešený překlad lepší?
$front[] = new Route('//<locale>/<newsStatic>/<newsUrl>', array(
'presenter' => 'News',
'action' => 'detail',
null => [
Route::FILTER_IN => function (array $params) {
$params['newsStatic'] = null;
if ($params['locale'] == 'tralala.cz'){
$params['locale'] = 'cs';
}
if($params['locale'] == 'abcd.com'){
$params['locale'] = 'en';
}
return $params;
},
Route::FILTER_OUT => function (array $params) {
if ($params['locale'] == 'cs'){
$params['locale'] = 'tralala.cz';
$params['newsStatic'] = 'novinky';
}
if($params['locale'] == 'en'){
$params['locale'] = 'abcd.com';
$params['newsStatic'] = 'news';
}
return $params;
},
],
));
- David Matějka
- Moderator | 6445
Automatické připojování jsem zkoušel takto. Myslelo se to s tímhle způsobem, nebo jinak?
tak by to taky slo, ale myslel jsem to zhruba nasledovne:
public function createRoute($mask, $metadata = [], $flags = 0)
{
$metadata[null][Route::FILTER_IN] = function ($params) use ($metadata) {
// spolecny IN filtr pro zpracovani params
// $params['foo'] = 'bar'
if ($params === null) {
return null;
}
//pro konkretni routu muzu mit specificky globalni filtr. ten uz obdrzi transformovane parametry z toho hlavniho
if (isset($metadata[null][Route::FILTER_IN])) {
return call_user_func($metadata[null][Route::FILTER_IN], $params);
}
return $params;
};
$metadata[null][Route::FILTER_OUT] = function ($params) use ($metadata) {
// custom OUT filtr pro routu
if (isset($metadata[null][Route::FILTER_OUT])) {
$params = call_user_func($metadata[null][Route::FILTER_OUT], $params);
if ($params === null) {
return null;
}
}
// spolecny OUT filtr
return $params;
};
return new Route($mask), $metadata, $flags);
}
a ty routy pak budes vytvaret pomoci tehle funkce.
- Laxren
- Člen | 23
Funguje to bezvadně, mockrát děkuju:)
Jen ještě jedna menší věc.
Jak se přeloží nějaký řetězec v masce?
Tahle metoda asi není ideální a plně funkční, protože pak když cokoliv
napíšu neexistujícího za doměnu → domena.com/dfsdfsdf
, tak
to přesměruje na domena.com/news
, ale jinak se zdá, že to
funguje.
$front[] = new Route('//<locale>/<newsStatic>', array(
'presenter' => 'News',
'action' => 'default',
null => [
Route::FILTER_IN => function (array $params) {
$params['newsStatic'] = null;
return $params;
},
Route::FILTER_OUT => function (array $params) {
if ($params['locale'] == 'cs'){
$params['newsStatic'] = 'novinky';
}
if($params['locale'] == 'en'){
$params['newsStatic'] = 'news';
}
return $params;
},
],
));
- David Matějka
- Moderator | 6445
do FILTER_IN bych dal neco jako:
if (!in_array($params['newStatic'], ['novinky', 'news'])) {
return null;
}
- Laxren
- Člen | 23
EDIT:
- Tracy hlásí
Undefined index: locale
u OUT filteru - Když skipnete u Tracy errory, tak na 404 erroru to nezná žádné routy
(odkazy) a v debugpanelu to píše
PHP User Warning: Invalid link: No route for...
aPHP Notice: Undefined index: locale in...RouterFactory.php
- Obecné routování úplně nefunguje, že to bere jen v potaz /<presenter>/<action> a k delším neexistujícím odkazům to nenajde žádnou routu a tedy to pak nenajde ani nastavený parametr locale a vezme to nastavenou lokaci ze Accept-Language , což je blbě.
Tyhle problémy jsou kdyžtak jen u 404, jinak vše všude na webu funguje.
Takže otázky asi zní ..
- Jak udělat obecnou routu pro absolutní routování, aby to matchnulo i při delším neexistujícím odkazu.
- Jak opravit odkazy u 404 erroru?
- jak opravit Undefined index: locale u OUT filteru
Eror presentery a templates jsou mimo moduly, jinak vše je ve
FrontModule
RouterFactory.php
<?php
namespace App;
use Nette;
use Nette\Application\Routers\RouteList;
use Nette\Application\Routers\Route;
/**
* Class RouterFactory
* @package App
*/
class RouterFactory {
use Nette\StaticClass;
const domenaCom = 'domena.com';
const tralaCz = 'trala.cz';
/**
* @return Nette\Application\IRouter
*/
public static function createRouter() {
$router = new RouteList;
$router[] = $front = new RouteList('Front');
$front[] = self::createRoute('//<locale>', array(
'presenter' => 'Homepage',
'action' => 'default'
));
$router[] = self::createRoute('//<locale>/<presenter>/<action>', array(
'presenter' => null,
'action' => null
));
return $router;
}
/**
* @param $mask
* @param array $metadata
* @param int $flags
* @return Route
*/
public static function createRoute($mask, $metadata = [], $flags = 0)
{
$metadata[null][Route::FILTER_IN] = function ($params) use ($metadata) {
if ($params === null) {
return null;
}
if ($params['locale'] == self::domenaCom){
$params['locale'] = 'en';
}elseif($params['locale'] == self::tralaCz){
$params['locale'] = 'cs';
}
if (isset($metadata[null][Route::FILTER_IN])) {
return call_user_func($metadata[null][Route::FILTER_IN], $params);
}
return $params;
};
$metadata[null][Route::FILTER_OUT] = function ($params) use ($metadata) {
if (isset($metadata[null][Route::FILTER_OUT])) {
$params = call_user_func($metadata[null][Route::FILTER_OUT], $params);
if ($params === null) {
return null;
}
}
if ($params['locale'] == 'en'){
$params['locale'] = self::domenaCom;
}elseif($params['locale'] == 'cs'){
$params['locale'] = self::tralaCz;
}
return $params;
};
return new Route($mask, $metadata, $flags);
}
}
- Laxren
- Člen | 23
Nakonec se to vyřešilo a dávám hotové řešení..
CreateRouter
/*Obecná routa pro Front module*/
$router[] = $front = new RouteList('Front');
$front[] = new Route('//<domain>/<presenter .+>/<action .+>', array(
'presenter' => 'Homepage',
'action' => 'default',
));
/*Obecná routa pro errorpresentery*/
$router[] = new Route('//<domain>/<presenter .+>/<action .+>', array(
'presenter' => 'Homepage',
'action' => 'default',
));
Podmínka .+ tam je kvůli tomu, že to nenalezlo locale při zadání
neexistující url začínající nějakou číslicí (domena.com/554554).
Přesně nevím proč to nefungovalo.
A taky kvůli tomu, že to nematchlo žádnou Routu při delší neexistující
url, s tímhle to už jde.
__________
Globální FILTER_IN
if ($params['domain'] === 'domena.cz') {
$params['locale'] = 'cs';
} elseif ($params['domain'] === 'abcd.com') {
$params['locale'] = 'en';
}
Globální FILTER_OUT
if (isset($params['locale']) && $params['locale'] === 'cs') {
$params['domain'] = 'domena.cz';
} elseif (isset($params['locale']) && $params['locale'] === 'en') {
$params['domain'] = 'abcd.com';
}
__________
Error4xxPresenter
public function startup()
{
//Předání locale
$this->locale = $this->translator->getLocale();
parent::startup();
if (!$this->getRequest()->isMethod(Nette\Application\Request::FORWARD)) {
$this->error();
}
}
Do Error4xxPresenteru se musel předat locale ručně, protože ho to jinak neznalo.
To je asi vše.
Kdyby k tomu někdo něco měl, tak budu rád..