I'm using Laravel localization to provide two different languages. I've got all the path stuff set up, and mydomain.com/en/bla delivers English and stores the 'en' session variable, and mydomain.com/he/bla delivers Hebrew and stores the 'he' session variable. However, I can't figure out a decent way to provide a language-switching link. How would this work?
I've solved my problem by adding this to the before filter in routes.php:
// Default language ($lang) & current uri language ($lang_uri)
$lang = 'he';
$lang_uri = URI::segment(1);
// Set default session language if none is set
if(!Session::has('language'))
{
Session::put('language', $lang);
}
// Route language path if needed
if($lang_uri !== 'en' && $lang_uri !== 'he')
{
return Redirect::to($lang.'/'.($lang_uri ? URI::current() : ''));
}
// Set session language to uri
elseif($lang_uri !== Session::get('language'))
{
Session::put('language', $lang_uri);
}
// Store the language switch links to the session
$he2en = preg_replace('/he\//', 'en/', URI::full(), 1);
$en2he = preg_replace('/en\//', 'he/', URI::full(), 1);
Session::put('he2en', $he2en);
Session::put('en2he', $en2he);
This is a post i posted originally on the laravel forums, but maybe it will help somebody else, so i post it here also.
I had some trouble with building a easy language switcher for my app, and the info on the forums where a little bit old (some posts), so i made this simple piece of code that makes it supereasy to change language on your app on the fly.
I have the language strings in my views as following:
{{ __('languagefile.the_language_string'); }}
And I get the languages with a URL, i think this is the best way, also its good for seo and for links that people share. Example:
www.myapp.com/fi/support (Finnish)
www.myapp.com/en/support (English)
www.myapp.com/sv/support (Swedish)
Ok, so the problem was that i wanted a easy way to change the language on the fly, without having to mess with sessions and cookies. Heres how i made it:
Make a library in your libraries folder called chooselang.php
Insert this code inside:
class Chooselang extends HTML {
/**
* Generate a Language changer link.
*
* <code>
* // Generate a link to the current location,
* // but still change the site langauge on the fly
* // Change $langcode to desired language, also change the Config::set('application.language', 'YOUR-LANG-HERE')); to desired language
* // Example
* echo Chooselang::langslug(URI::current() , $langcode = 'Finnish' . Config::set('application.language', 'fi'));
* </code>
*
* #param string $url
* #param string $langcode
* #param array $attributes
* #param bool $https
* #return string
*/
public static function langslug($url, $langcode = null, $attributes = array(), $https = null)
{
$url = URL::to($url, $https);
if (is_null($langcode)) $langcode = $url;
return '<a href="'.$url.'"'.static::attributes($attributes).'>'.static::entities($langcode).'</a>';
}
}
After this you are ready for getting your url switcher URL:s generated. Simply add them as you whould any other Blade links.
Example how to generate links for Finnish, Swedish and English (with Blade)
{{ Chooselang::langslug(URI::current() , $langcode = 'Fin' . Config::set('application.language', 'fi')); }}
{{ Chooselang::langslug(URI::current() , $langcode = 'Swe' . Config::set('application.language', 'sv')); }}
{{ Chooselang::langslug(URI::current() , $langcode = 'Eng' . Config::set('application.language', 'en')); }}
The above will generate URL:s that are always on the current page, and change the lang slug to the one you want. This way the language changes to the one you want, and the user naturally stays on the same page. The default language slug is never added to the url.
Generated urls look something like:
Fin
Swe
Eng
PS. The links are specially useful if you add them to your master template file.
You could have a Route to hand language change, for example:
Route::get('translate/(:any)', 'translator#set');
Then in the set action in the translator controller could alter the session, depending on the language code passed via the URL.
You could also alter the configuration setting by using
Config::set('application.language', $url_variable');
Controller Example - translate.php
public function action_set($url_variable)
{
/* Your code Here */
}
Just in case for future users if you want to use package for localization There is a great package at https://github.com/mcamara/laravel-localization. which is easy to install and has many helpers.
This question still comes in Google search, so here's the answer if you're using Laravel 4 or 5, and mcamara/laravellocalization.
<ul>
<li class="h5"><strong><span class="ee-text-dark">{{ trans('common.chooselanguage') }}:</span></strong> </li>
#foreach(LaravelLocalization::getSupportedLocales() as $localeCode => $properties)
<li>
<a rel="alternate" hreflang="{{$localeCode}}" href="{{LaravelLocalization::getLocalizedURL($localeCode) }}">
<img src="/img/flags/{{$localeCode}}.gif" /> {{{ $properties['native'] }}}
</a>
</li>
#endforeach
</ul>
NOTE that this example shows flags (in public/img/flags/{{locale}}.gif), and to use it you will need a bit of .css, but you can modify it to display the text if you want...
FYI. The mcamara/laravellocalization documentation has examples and a LOT of helpers, so look through the documentation on github. (https://github.com/mcamara/laravel-localization)
Try use Session's. Somthing like this:
Controller:
class Language_Controller extends Base_Controller {
function __construct(){
$this->action_set();
parent::__construct();
}
private function checkLang($lang = null){
if(isset($lang)){
foreach($this->_Langs as $k => $v){
if(strcmp($lang, $k) == 0) $Check = true;
}
}
return isset($Check) ? $Check : false;
}
public function action_set($lang = null){
if(isset($lang) && $this->checkLang($lang)){
Session::put('lang', $lang);
$this->_Langs['current'] = $lang;
Config::set('application.language', $lang);
} else {
if(Session::has('lang')){
Config::set('application.language', Session::get('lang'));
$this->_Langs['current'] = Session::get('lang');
} else {
$this->_Langs['current'] = $this->_Default;
}
}
return Redirect::to('/');
}
}
In Route.php:
Route::get('lang/(:any)', 'language#set');
I've been doing it like this:
$languages = Config::get('lang.languages'); //returns array('hrv', 'eng')
$locale = Request::segment(1); //fetches first URI segment
//for default language ('hrv') set $locale prefix to "", otherwise set it to lang prefix
if (in_array($locale, $languages) && $locale != 'hrv') {
App::setLocale($locale);
} else {
App::setLocale('hrv');
$locale = null;
}
// "/" routes will be default language routes, and "/$prefix" routes will be routes for all other languages
Route::group(array('prefix' => $locale), function() {
//my routes here
});
Source: http://forumsarchive.laravel.io/viewtopic.php?pid=35185#p35185
What I'm doing consists of two steps:
I'm creating a languages table which consists of these fields:
id | name | slug
which hold the data im gonna need for the languages for example
1 | greek | gr
2 | english | en
3 | deutch | de
The Language model I use in the code below refers to that table.
So, in my routes.php I have something like:
//get the first segment of the url
$slug = Request::segment(1);
$requested_slug = "";
//I retrieve the recordset from the languages table that has as a slug the first url segment of request
$lang = Language::where('slug', '=', $slug)->first();
//if it's null, the language I will retrieve a new recordset with my default language
$lang ? $requested_slug = $slug : $lang = Language::where('slug', '=', **mydefaultlanguage**')->first();
//I'm preparing the $routePrefix variable, which will help me with my forms
$requested_slug == ""? $routePrefix = "" : $routePrefix = $requested_slug.".";
//and I'm putting the data in the in the session
Session::put('lang_id', $lang->id);
Session::put('slug', $requested_slug);
Session::put('routePrefix', $routePrefix );
Session::put('lang', $lang->name);
And then I can write me routes using the requested slug as a prefix...
Route::group(array('prefix' => $requested_slug), function()
{
Route::get('/', function () {
return "the language here is gonna be: ".Session::get('lang');
});
Route::resource('posts', 'PostsController');
Route::resource('albums', 'AlbumsController');
});
This works but this code will ask the database for the languages everytime the route changes in my app.
I don't know how I could, and if I should, figure out a mechanism that detects if the route changes to another language.
Hope that helped.
Related
Former I had created my own MVC and there was an index.php that all pages passed from the way of it. I mean, I could put a redirect (header('Location: ..');) into index.php, and then none of my website pages can not be opened.
Now I use Laravel and I need a core-page (like index.php). Because my new website supports multi-languages. Here is my current code:
// app/Http/routes.php
Route::get('/{locale?}', 'HomeController#index');
// app/Http/Controllers/HomeController.php
public function index($locale = null)
{
if(!is_null($locale)) {
Lang::setLocale($locale);
}
dd(Lang::getLocale());
/* http://localhost:8000 => output: en -- default
* http://localhost:8000/abcde => output: en -- fallback language
* http://localhost:8000/en => output: en -- defined language
* http://localhost:8000/es => output: es -- defined language
* http://localhost:8000/fa => output: fa -- defined language
*/
}
As you see, in my current algorithm, I need to check the language that the user has set for each route. Also my website has almost 30 routes. I can do that manually 30 times for each route, but I think there is a way which lets me to do that once for all routes. Isn't there?
In other word, how can I set current language (the user has set) for each page? Should I check/set it for each route separately?
There is a smarter way in Laravel for solve your trouble. It is called Middleware. So you can just create LangMiddleware and put your logic inside.
Something like
public function handle($request, Closure $next, $locale = null)
{
if ($locale && in_array($locale, config('app.allowed_locales'))) {
Lang::setLocale($locale);
}
else{
Lang::setLocale(config('app.fallback_locale'));
}
return $next($request);
}
I`m using zend framework and my urls are like this :
http://target.net/reward/index/year/2012/month/11
the url shows that I'm in reward controller and in index action.The rest is my parameters.The problem is that I'm using index action in whole program and I want to remove that part from URL to make it sth like this :
http://target.net/reward/year/2012/month/11
But the year part is mistaken with action part.Is there any way ?!!!
Thanks in advance
Have a look at routes. With routes, you can redirect any URL-format to the controller/action you specify. For example, in a .ini config file, this will do what you want:
routes.myroute.route = "reward/year/:myyear/month/:mymonth"
routes.myroute.defaults.controller = reward
routes.myroute.defaults.action = index
routes.myroute.defaults.myyear = 2012
routes.myroute.defaults.mymonth = 11
routes.myroute.reqs.myyear = "\d+"
routes.myroute.reqs.mymonth = "\d+"
First you define the format the URL should match. Words starting with a colon : are variables. After that you define the defaults and any requirements on the parameters.
you can use controller_plugin to control url .
as you want create a plugin file (/library/plugins/controllers/myplugin.php).
then with preDispatch() method you can get requested url elements and then customize that for your controllers .
myplugin.php
class plugins_controllers_pages extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$int = 0;
$params = $this->getRequest()->getParams();
if($params['action'] != 'index' ) AND !$int)
{
$int++;
$request->setControllerName($params['controller']);
$request->setActionName('index');
$request->setParams(array('parameter' => $params['action']));
$this->postDispatch($request);
}
}
}
I am currently working on a API allowing users to get some event messages on their devices. Users call my API specifying me their preferred locale (for instance fr_FR).
On the back-office, administrators can choose the languages they want to support. Let's assume they choose English and Chinese. So, they do not support French language.
I am using the Propel I18N behavior to deal with all the translations. In my requests, I am currently doing:
SystemMessageQuery::create()
->joinWithI18N('fr_FR')
->findOne();
It works correctly if I specify an existing locale. Yet, if the translation is not available, it returns null. Sounds logical.
My question is: can I fallback the user on a default locale (in this case, en_US) if the provided locale is not planned by an administrator? If yes, how can I proceed?
I thought to send a prior request to check if the locale exists. But I do not think it is a good way for performances reasons (especially for an API).
Any idea?
According to the documentation, there is not such a fallback in Propel.
What you can try, is the combination of setLocale and joinWithI18N. Something like:
$default_locale = 'en_US';
$items = ItemQuery::create()
->joinWithI18n('fr_FR')
->find();
foreach ($items as $item)
{
echo $item->getPrice();
// test if name is translated
if (!$name = $item->getName())
{
// otherwise, try to get the translation with default locale
$item->setLocale($default_locale);
$name = $item->getName(); // one query to retrieve the English translation
}
}
I haven't tested this code but it seems to be a solution.
Also, have you tried to add two joinWithI18N (one for en_US & one for fr_FR) to see how the query handle it?
Finally, I used the following code:
public function setLocale($locale = 'en_US', $fallbackEnabled = false)
{
if($fallbackEnabled) {
$localeExists = SystemMessageI18nQuery::create()
->filterByLocale($locale)
->filterById($this->getId())
->count();
if(!$localeExists) {
$locale = SystemMessageI18nQuery::create()
->filterById($this->getId())
->select('locale')
->findOne();
}
}
parent::setLocale($locale);
}
It is not the best solution for performance reasons, but it is functionnal.
Try something like this
public function joinWithI18nFallback($locale=null, $joinType = Criteria::LEFT_JOIN, $default_locale=null)
{
$languages = sfConfig::get('sf_languages');
if(null == $locale)
{
$locale = sfPropel::getDefaultCulture();
}
if(null == $default_locale)
{
$default_locale = sfPropel::getDefaultCulture();
}
$languages[] = $locale;
$languages[] = $default_locale;
$languages = array_unique($languages);
$FallbackQuery = CategoryQuery::create()
->leftJoin('CategoryI18n')
->addJoinCondition('CategoryI18n', "CategoryI18n.Culture IN ('".implode("','", $languages)."')" )
->withColumn('CategoryI18n.Culture', 'CultureDefault')
->addAscendingOrderByColumn("FIELD(category_i18n.culture, '".$locale."', '".implode("','", $languages)."' )")
->where("CategoryI18n.name != ''");
$this->addSelectQuery($FallbackQuery, 'Category')
->join('CategoryI18n', $joinType)
->addJoinCondition('CategoryI18n', 'CategoryI18n.Culture = Category.CultureDefault' )
->with('CategoryI18n')
->groupById();
$this->with['CategoryI18n']->setIsWithOneToMany(false);
return $this;
}
I have project-range meta tags that are need to be set.
I've put them in protected method _initMeta in Bootstrap class.
Are there any better options? What if I would like different set of this data for another languages?
protected function _initMeta(){
$this->bootstrap('view');
$view = $this->getResource('view');
$view->doctype('XHTML1_STRICT');
$view->headTitle()->headTitle('Foo title');
$view->headMeta()->appendName('keywords','foo');
$view->headMeta()->appendHttpEquiv('Content-Type', 'text/html; charset=UTF-8')
->appendHttpEquiv('Content-Language', 'any');
$view->headLink()->appendStylesheet('/foo.css')->headLink(array('rel' => 'favicon',
'href' => '/favicon.ico'),
'PREPEND');
}
I use config for basic (bootstrap) data as:
application.ini
resources.view.meta.name.Viewport = "width=device-width, initial-scale=1.0"
resources.view.meta.name.MobileOptimized = "width"
resources.view.meta.name.HandheldFriendly = "true"
resources.view.meta.name.Keywords = "basic,keywords"
...
; format resources.view.headStyle.{MEDIA}.nfile =
resources.view.headStyle.all.1.href = "/css/basic.css"
resources.view.headStyle.all.1.conditionalStylesheet =
resources.view.headStyle.all.1.extras.title = "Basic style"
resources.view.headStyle.all.1.extras.charset = "utf-8"
resources.view.headStyle.all.2.href = "/css/ie.css"
resources.view.headStyle.all.2.conditionalStylesheet = "IE"
resources.view.headStyle.all.2.extras.title = "Internet Explorer style"
resources.view.headStyle.all.2.extras.charset = "utf-8"
; print media example
resources.view.headStyle.print.1.href = "/css/print.css"
...
; format resources.view.headLink.{REL} =
resources.view.headLink.humans.href = "/humans.txt"
resources.view.headLink.humans.type = "text/plain"
; ___ will be replaced by space, __ by point (or set another nest separator)
resources.view.headLink.shortcut___icon.href = "/favicon.png"
resources.view.headLink.shortcut___icon.type = "image/png"
...
At this point, maybe you have some special data. For example in:
project1.ini
project.headLink.author.href = "https://plus.google.com/XXXXX?rel=author"
project.headLink.image_src.href = "/author.jpg"
project.headLink.image_src.type = "image/jpg"
And finally, you mix all in your
Bootstrap.php
(example for *_initHeadLink()*):
// $options = your app options (basic)
// $projectOptions = your project options (special)
// $assets_url = your assets url
if ( is_array($headStyle = $options['headStyle']) ) {
foreach ( $headStyle as $media => $value ) {
foreach ( $value as $style ) {
extract($style);
$this->view->headLink()->appendStylesheet($assets_url . $href, $media,
$conditionalStylesheet, $extras);
}
}
}
$headLinks = array();
if ( isset($options['headLink']) )
$headLinks = $options['headLink'];
if ( isset($projectOptions['headLink']) )
$headLinks = array_merge($headLinks, (array) $projectOptions['headLink']);
// *array key, is the value for rel
foreach ( $headLinks as $rel => $value ) {
$rel = str_replace(array('___', '__'), array(' ', '.'), $rel);
$this->view->headLink()->headLink(array_merge(array('rel' => $rel), (array) $value));
}
Then, you can override these data from your Controller: setName, set...
I hope it helps ;)
You have several ways of achieving this. First and foremost, make sure the moment you define the metadata you already know what language will be loaded for the current request. Sometimes this may not be easy to determine at bootstrap time.
Having said this, besides the bootstrap, you can set the metadata on a:
Front Controller Plugin class (using for example the dispatchLoopStartup() or predispatch() methods).
Action Helper class (using for example the init() or preDispatch() methods).
At those points of execution you probably already determined the language to use, and can set the metadata accordingly. You can always change the metadata afterwards in your action controllers for specific cases in your application, so you're never really stuck if you previously specified metadata.
In my own work, I have this setup:
Front Controller Plugin, dispatchLoopStartup() method: determine language to load, giving priority to a "lang" GET parameter in the request object, then browser language, then default site language. I also use this to determine if the request is a normal request or an ajax request; if it's the former case, I register an action helper, see next...
Action Helper, preDispatch() method: load metadata depending on language and other stuff, load Layout and widgets, etc.
Action controller, someAction() method: if necessary, change some of the previously set metadata, for example headTitle(), which can depend on the effectively loaded content.
This arrangement makes sense to me, maybe it can help your approach?
The bootstrap is way to early for my projects. I add them in my controller/actions
$keywords = 'php,zend,framework';
$this->view->headMeta($keywords,'keywords','name',array(),'SET');
... etc.
Actually very late almost at the end. At this point I would also know about language and other things.
I have a mobile page running on a subdomain "m.mydomain.com". This is all working fine, but I would like to remove the controller in the URL when using the subdomain.
m.mydomain.com/mobiles/tips
should become
m.mydomain.com/tips
by using the HTML-Helper.
At the moment a link looks like that:
$html->link('MyLink', array('controller' => 'mobiles', 'action'=> 'tips'));
I tried several possible solutions with the routes and also some hacks in the bootstrap but it did not work out for me.
In the CakeBakery I found this but that does not solve my issue.
Does anyone have an idea for this issue?
Gathering code from the page you mentioned:
Constraint: you cannot have a controller called tips or foo in this setup
In /config/routes.php:
$subdomain = substr( env("HTTP_HOST"), 0, strpos(env("HTTP_HOST"), ".") );
if( strlen($subdomain)>0 && $subdomain != "m" ) {
Router::connect('/tips',array('controller'=>'mobiles','action'=>'tips'));
Router::connect('/foo', array('controller'=>'mobiles','action'=>'foo'));
Configure::write('Site.type', 'mobile');
}
/* The following is available via default routes '/{:controller}/{:action}'*/
// Router::connect('/mobiles/tips',
// array('controller' => 'mobiles', 'action'=>'tips'));
// Router::connect('/mobiles/foo',
// array('controller' => 'mobiles', 'action'=>'foo'));
In your Controller action:
$site_is_mobile = Configure::read('Site.type') ?: '';
Then in your view:
<?php
if ( $site_is_mobile ) {
// $html will take care of the 'm.example.com' part
$html->link('Cool Tips', '/tips');
$html->link('Hot Foo', '/foo');
} else {
// $html will just output 'www.example.com' in this case
$html->link('Cool Tips', '/mobiles/tips');
$html->link('Hot Foo', '/mobiles/foo');
}
?>
This will allow you to output the right links in your views (in a bit I'll show you how to write even less code) but the $html helper will not be able -- by no amount of magic -- to use controller-action routes to another domain. Be aware that m.example.com and www.example.com are different domains as far as the $html helper is concerned.
Now, if you want you can do the following in your controller to take some logic off your view:
<?php
$site_is_mobile = Configure::read('Site.type') ?: '';
if ( $site_is_mobile !== '' ) {
$tips_url = '/tips';
$foo_url = '/foo';
} else {
$tips_url = '/mobile/tips';
$foo_url = '/mobile/foo';
}
// make "urls" available to the View
$this->set($tips_url);
$this->set($foo_url);
?>
And in your view you don't need to worry about checking whether the site is being accessed via m.example.com/tips or www.example.com/mobile/tips:
<?php echo $html->link("Get some kewl tips", $tips_url); ?>
For more advanced routing in CakePHP-1.3 refer to Mark Story's article on custom Route classes
Let us know ;)