Vícejazyčné routování – Pro každý jazyk jiná doména, překlady rout, modul pro jeden jazyk

Laxren
Člen | 23
+
0
-

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

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

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

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

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

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

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

do FILTER_IN bych dal neco jako:

if (!in_array($params['newStatic'], ['novinky', 'news'])) {
	return null;
}
Laxren
Člen | 23
+
0
-

Super.
Ještě jednou děkuju za spolupráci.

Laxren
Člen | 23
+
0
-

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

  1. Jak udělat obecnou routu pro absolutní routování, aby to matchnulo i při delším neexistujícím odkazu.
  2. Jak opravit odkazy u 404 erroru?
  3. 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
+
0
-

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