Multilingual site with database and PHP - php

I am trying to come up with an efficient way to implement language selection in my site.
I have some flags at the top right which when clicked I want all the text to change into the selected language, where the translations are stored in my database.
Should I do this with a parameter in the url like:
www.myside.com?lang=3
The only issue I have with this, is that it might things complicated as far as the way I route urls and it doesn't make the url look clean either.
Would a better way, be to have it stored in a session and the translations are only fetched from the database when the language is changed. The translations would be kept in a session array, so users don't hit the database on every page load if you know what I mean.
I was wondering if something like the following would be a good way of achieving what I want:
Session::set('langarray', array(
'id' => $languageId,
'cake' => $this->model->getLanguagesNavigation('cake', $languageId),
'login' => $this->model->getLanguagesNavigation('login', $languageId),
'register' => $this->model->getLanguagesNavigation('register', $languageId),
'share' => $this->model->getLanguagesNavigation('share', $languageId),
'galleries' => $this->model->getLanguagesNavigation('galleries', $languageId),
'decorator' => $this->model->getLanguagesNavigation('decorator', $languageId),
'find' => $this->model->getLanguagesContent('find', $languageId),
'headertext' => $this->model->getLanguagesContent('headerText', $languageId),
));
header('Location: ' . $_SERVER['HTTP_REFERER']);
and in my view:
...
public function render($viewFile, $data = NULL) {
if(!Session::get('langarray'))
{
$this->Language = new Language;
$this->Language->setLanguage(1);
}
if (is_array($data)) {
extract($data);
}
include Vs . $viewFile . '.php';
}
...
Which is simply set the language to 1 (English) if the session var hasn't been set i.e. a language hasn't been picked.
In my HTML I would just echo the corresponding element in the array to get the word:
...
<p><?PHP echo $_SESSION['langarray']['headertext'];?></p>
...
Is this a good method? Or is there a standard way of implementing languages into a site?
My old site currently uses an url method like the one I mentioned (?lang=3) and the foreign variants do quite well in the SEs. I like the idea of using subdomains, but how would I get it to display the correct content on my pages based on whatever come before the first . in the url? E.g. fr. de. etc

Maybe I'm old fashioned but I've always had a lang folder with files for each languages (lang.en.php, lang.fr.php, lang.es.php, and so on).
In each file I've got an array, like this one:
$langarray = array("login" => "...", "connect" => "...", "logout" => "...");
Eventually with real stuff in them... works better :}
And then, depending on the $_SESSION variable, I include the right file. You can even stock "en" and include lang.'.$_SESSION['lang'].'.php.
It seems slightly better not having to query SQL for that kind of thing but less easy to maintain. I think this is the type of problem where nobody's wrong.

Yes, saving the language code in the session is better than constantly passing around a parameter.
No, storing the translated text in the session doesn't make sense because then you are storing the same text over and over in memory per user. Better to implement database caching or have a PHP file to include for the translation table than to store it in the session.
Instead of making up numeric codes for languages, you really should use the standard letter abbreviations that are part of the HTML spec. Browsers send preferred languages in order of preference as a header called Accept-Language. To avoid making the user click a language choice, you could read that list from the header and iterate through it until you find the first language you support. But always good to give the user some manual way to change it.
Zend framework has some functions for dealing with languages but I've never used it.

As I mentioned in my comment above, I would recommend putting the language in the URL mainly for search engine optimization. Admittedly, it can take a bit more work to set up, but I feel the benefits outweigh the cost.
For example, I have a hotel website with English, Spanish, Chinese and French. The URL structure is like this:
/ <- Main page in English
/es/ <- Main page in Spanish
/zh/ <- Main page in Chinese
/fr/ <- Main page in French
Then for sub-pages I do similarly:
/pagename/
/es/pagename/
/zh/pagename/
/fr/pagename/
Then, this is how I redirect the other languages to the correct scripts:
# Spanish
RewriteRule ^es/(.*/)?$ $1/index.php?lang=es [NC,L]
# French
RewriteRule ^fr/(.*/)?$ $1/index.php?lang=fr [NC,L]
# Chinese
RewriteRule ^zh/(.*/)?$ $1/index.php?lang=zh [NC,L]
This works for rewriting the index.php. For other things, I specify them explicitly, usually using a "friendly" URL. For example the "thank you" page for our Contact Us page:
# Contact Us thank you page
RewriteRule ^([a-z_]+)?/?contact/thankyou$ /contact/thankyou.php?lang=$1 [L]
I also used to do the following to rewrite URLs that contained parameters, but I think doing it like this might be deprecated, not too sure (I have it in the code, but since I don't use parameters, it doesn't get triggered):
RewriteCond %{query_string} ([^=]*)=(.*)
RewriteRule ^es/(.*)$ $1/index.php?lang=es&%1=%2 [NC,L]
However, the hotel offers tours and we have a main /tours/ page and I wanted to have a friendly URL for each of the individual tours, like /tours/56/waterfall-hike (with the tour ID and a slug from the tour name), so this handles the rewriting of the tours:
# Rewrite tours
RewriteRule ^([a-z_]+)?/?tours/([0-9]+)/reserve$ /tours/tour_reserve.php?lang=$1&id=$2 [L]
RewriteRule ^([a-z_]+)?/?tours/([0-9]+)/(.*) /tours/tour_text.php?lang=$1&id=$2&urlstr=$3 [L]
# Tours thank you page
RewriteRule ^([a-z_]+)?/?tours/thankyou$ /tours/thankyou.php?lang=$1 [L]
I just need to verify with PHP that the slug string provided is correct and if not do a 301 redirect to the correct URL based on the ID. I use this to calculate it:
function getTourURL($tour_name) {
// Transliterate non-ascii characters to ascii
$str = trim(strtolower($tour_name));
$str = iconv('UTF-8', 'ASCII//TRANSLIT', $str);
// Do other search and replace
$searches = array(' ', '&', '/');
$replaces = array('-', 'and', '-');
$str = str_replace($searches, $replaces, $str);
// Make sure we don't have more than one dash together
$str = preg_replace("/(-{2,})/", "-", $str );
// Remove all invalid characters
$str = preg_replace("/[^A-Za-z0-9-]/", "", $str );
// Done!
return $str;
}
Then the only other difficult thing is switching languages without just redirecting them back to the home page for that language (yuck!). This is how I did it:
// First define the languages used:
$langs = array(
'en' => array(
'lang_code' => 'en_US',
'locale' => 'en_US.UTF-8',
'base_url' => '/',
'name' => 'English',
),
'es' => array(
'lang_code' => 'es_MX',
'locale' => 'es_MX.UTF-8',
'base_url' => '/es/',
'name' => 'Español',
),
'fr' => array(
'lang_code' => 'fr_FR',
'locale' => 'fr_FR.UTF-8',
'base_url' => '/fr/',
'name' => 'Français',
),
'zh' => array(
'lang_code' => 'zh_CN',
'locale' => 'zh_CN.UTF-8',
'base_url' => '/zh/',
'name' => '中国的',
),
);
define('LOCALE', $_GET['lang']);
// Then get the path to the current script after the language code
$path = (LOCALE == 'en') ? '' : LOCALE.'/';
$parsed_url = parse_url($_SERVER['REQUEST_URI']);
$path = ltrim(str_replace('/'.LOCALE , '', $parsed_url['path']), '/');
define('REDIRECT_SCRIPT', $path);
// Then I put all the links into an array to be displayed in the menu
foreach ($langs as $lang => $arr) {
if ($lang == LOCALE) {
continue;
}
$link = (isset($lang_override[$lang]))
? $lang_override[$lang]
: $arr['base_url'] . REDIRECT_SCRIPT;
$lang_subs[] = array(
'name' => '<div class="'.$lang.'"></div> '.$langs[$lang]['name'],
'link' => $link,
'lang' => $lang,
);
}
This probably won't work for you out of the box, but should at least give you a starting point. Try a print_r($lang_subs); to see what it contains and adapt it to your site's design.

Related

Include problems with PHP

I need a little help! I'm making multilangual site with php and I got an issue. I want to include array into another array.
There is one php file
<?php
$lang = array(
'en' => array(
include ('translations/nav-translation/en.php');
),
'lv' => array(
include ('translations/nav-translation/lv.php');
),
);
?>
And php file that I want to include
<?php
"home" => "Home",
"blog" => "Blog",
"about" => "About me",
?>
If you don't want to use yaml it's better to return arrays from your lang files
en.php:
<?php
return ["home" => "Home",....]
?>
index.php:
<?php
$lang = array(
'en' => include("translations/nav-translation/en.php"),
'lv' => ....
Can't you just set up your include files like this:
translations/nav-translation/en.php
<?php
$lang['en'] = array(
"home" => "Home",
"blog" => "Blog",
"about" => "About me"
);
?>
And then:
<?php
$lang = array();
include ('translations/nav-translation/en.php');
include ('translations/nav-translation/lv.php');
?>
Seems a lot less complicated than any other suggestion.
To create a multilingual PHP site, I'd suggest using a function that checks the language the user has specified and then loading the text from a database or use JSON.
Using JSON
Create a file with a .txt extension, create your translations in JSON format and read it via PHP, to read the file in PHP use:
$json = json_decode(file_get_contents($file),TRUE);
You can then use the JSON data to set variables within PHP to display the correct language.
Using a database
Have your database setup like this:
---------------------------
Lang | Short | Text
---------------------------
In your database, set up as many languages as you want, have the lang column the two-letter abbreviation of the language name, the short should be the name of the text, so if it was the home page title, name the short home-title and then the text should be the text that you want to be displayed in your desired language.
Using a database will allow you to add/edit languages and translations via the database.
$path = 'translations/nav-translation';
$user_lang = 'nl'; // determine what the user actually wants
$fallback_lang = 'en';
if (file_exists("{$path}/{$user_lang}.php")) {
include ("{$path}/{$user_lang}.php");
} else {
include ("{$path}/{$fallback_lang}.php"); // nominate a default
}
In the language files (e.g. translations/nav-translation/en.php), offer a fully formed associative array:
$lang = [
"home" => "Home",
"blog" => "Blog",
"about" => "About me"
];
Then, back in your original file after the conditional include call, you can reference $lang['home'] or [cringe] use extract()* to generate variables like $home.
*note, I have never, ever used extract() in any of my projects, there are risks in using it -- I'm just saying you could.

Zend Framework - setting up user-friendly URLs with routes and regex

I have two issues with user-friendly URLs.
I have a router set up as follows:
The actual URL is http://www.example.com/course/view-batch/course_id/19
I want a friendlier URL http://www.example.com/java-training/19
I have setup the following route in application.ini:
resources.router.routes.viewbatchcourse.route = "/:title/:course_id/"
resources.router.routes.viewbatchcourse.defaults.controller = course
resources.router.routes.viewbatchcourse.defaults.action = view-batch
resources.router.routes.viewbatchcourse.reqs.course_id = "\d+"
This works perfectly well.
Now I have a new page - which contains user reviews for Java
The actual URL is http://www.example.com/course/view-reviews/course_id/19
I want a friendlier URL http://www.example.com/java-reviews/19
I realize its not possible because one route is already setup to match that format.
So I was thinking if its possible to use regex and check if title contains "reviews" then use this route.
I tried this approach, but it doesn't work. Instead, it opens the view-batch page:
resources.router.routes.viewreviews.type = "Zend_Controller_Router_Route_Regex"
resources.router.routes.viewreviews.route = "/:title/:course_id"
resources.router.routes.viewreviews.defaults.controller = "course"
resources.router.routes.viewreviews.defaults.action = "view-reviews"
resources.router.routes.viewreviews.reqs.course_id = "\d+"
resources.router.routes.viewreviews.reqs.title = "\breviews\b"
The closest I have got this to work is
resources.router.routes.viewreviews.route = "/:title/:course_id"
resources.router.routes.viewreviews.defaults.controller = "course"
resources.router.routes.viewreviews.defaults.action = "view-reviews"
resources.router.routes.viewreviews.reqs.course_id = "\d+"
resources.router.routes.viewreviews.reqs.title = "reviews"
Now if I enter the URL http://www.example.com/reviews/19, then the view-reviews action gets called.
Is it possible - to check if title contains the word "reviews" - then this route should be invoked?
Going back to my earlier working route for http://www.example.com/java-training/19:
resources.router.routes.viewbatchcourse.route = "/:title/:course_id/"
resources.router.routes.viewbatchcourse.defaults.controller = course
resources.router.routes.viewbatchcourse.defaults.action = view-batch
resources.router.routes.viewbatchcourse.reqs.course_id = "\d+"
The number 19 is the course id, which I need in the action to pull the details from the database.
But when the page is displayed, I dont want the number 19 visible.
I just want the URL to be http://www.example.com/java-training
Is this possible?
1) You can use Route_Regex to achieve what you want
protected function _initRoutes()
{
$router = Zend_Controller_Front::getInstance()->getRouter();
$route = new Zend_Controller_Router_Route_Regex(
'([a-zA-Z]+)-reviews/(\d+)',
array(
'module' => 'default',
'controller' => 'course',
'action' => 'view-reviews'
),
array(
1 => 'language',
2 => 'course_id',
)
);
$router->addRoute('review', $route);
$route = new Zend_Controller_Router_Route_Regex(
'([a-zA-Z]+)-training/(\d+)',
array(
'module' => 'default',
'controller' => 'course',
'action' => 'view-batch'
),
array(
1 => 'language',
2 => 'course_id',
)
);
$router->addRoute('training', $route);
}
2) For the second point I can't see how it can be possible as is.
One thing you could do though is to use the name of the course, if you have one, to display an url like :
www.xyz.com/java-training/my-awesome-course-19
www.xyz.com/java-training/19/my-awesome-course
It would be pretty easy using the routes i mentionned above.
for question 1. I think you can solve this problem quite simply by altering the route. You don't need to have :title as part of the route, instead it can be hard coded in your case. I would recommend the following configuration.
resources.router.routes.viewbatchcourse.route = "/java-training/:course_id/"
resources.router.routes.viewbatchcourse.defaults.controller = course
resources.router.routes.viewbatchcourse.defaults.action = view-batch
resources.router.routes.viewbatchcourse.defaults.title = java-training
resources.router.routes.viewbatchcourse.reqs.course_id = "\d+"
resources.router.routes.viewreviews.route = "/java-reviews/:course_id/"
resources.router.routes.viewreviews.defaults.controller = course
resources.router.routes.viewreviews.defaults.action = view-reviews
resources.router.routes.viewreviews.defaults.title = java-reviews
resources.router.routes.viewreviews.reqs.course_id = "\d+"
For question 2. As you describe it, simply no.
Re: Q1. I haven’t tested this, but hopefully it is pointing you in the direction you want to go.
resources.router.routes.viewreviews.type = "Zend_Controller_Router_Route_Regex"
resources.router.routes.viewreviews.route = "(.+)-reviews/(\d+)"
resources.router.routes.viewreviews.defaults.controller = "course"
resources.router.routes.viewreviews.defaults.action = "view-reviews"
resources.router.routes.viewreviews.map.1 = "title"
resources.router.routes.viewreviews.map.2 = "course_id"
Re: Q2. I think you'd need to either forward the user to another (parameterless) URL or handle this via javascript. See Modify the URL without reloading the page.

Yii: How to redirect and go to a specific element on page?

I am trying to redirect and go to a specific element on the new page like this:
http://192.168.0.49/x/y/index.php/admin/user/update/id/3#certificate
$this->redirect(array('update', 'id' => $certificate->user_id));
How can this be done?
You can simply create the url without the fragment part and then append it manually:
$url = Yii::app()->createUrl('update', ['id' => $certificate->user_id]);
$url .= "#certificate";
$this->redirect($url);
This code works in a manner that is immediately obvious when reading the code. Apart from that there is also the Yii-specific solution: CUrlManager (the component responsible for building URLs) also recognizes # as a parameter. So you can write:
$url = Yii::app()->createUrl(
'update',
['id' => $certificate->user_id, '#' => 'certificate']
);
That can't be done using redirect.
A work around would be
$url = Yii::app()->createUrl('update', array('id' => $certificate->user_id, '#' => "certificate"));
$this->redirect($url);

CakePHP: Redirect in routes.php

OK, I dont know if I am taking the wrong approach or not but am stuck here...
We have developed our website and we have many controllers expecting ids and special variables, links already redirecting to the controllers passing what is expected.
The new requirement is to use friendlyUrls and the idea is that instead of having:
http://domain.com/search/advanced/term:head/city:/set:show-all/sort:basic-relevance
it now reads
http://domain.com/search/head
or passing options.
http://domain.com/search/in-edinburgh-scotland/by-rating/head
My idea was to, at the beginning of the Routes.php have a simple if such as:
$friendlyUrl = $_SERVER['REQUEST_URI'];
$friendlyUrl = split('/', $friendlyUrl);
foreach ($friendlyUrl as $key => $params) {
if(empty($params)){
unset($friendlyUrl[$key]);
}
if($params == 'search'){
Router::connect('/search/*', array('plugin'=>'Search','controller' => 'Search', 'action' => 'advancedSearch', 'term'=>'head));
}elseif ($params == 'employers') {
# code...
}elseif ($params == 'employer-reviews') {
# code...
}elseif ($params == 'jobs') {
# code...
}
}
That didn't work, then I tried adding something similar in my AppController and nothing.
All in all the the thing that has to do is:
Url be in the format of: /search/{term}
Actually be redirecting to: /search/advanced/{term}/city:{optional}/set:show-all/sort:basic-relevance
URL bar to keep reading: /search/{term}
Anyone has an idea?! Thank you
You definitely want to have a look at the routing page in the book
http://book.cakephp.org/2.0/en/development/routing.html
There are tons of options there to match url patterns to pass parameters to the controllers.
Router::connect(
'/search/:term',
array('controller' => 'search', 'action' => 'advanced'),
array(
'pass' => array( 'term')
)
);
You should probably set the defaults for city & set & sort in the actions function parameters definitions:
public function advanced($term, $city='optional', $sort = 'basic'){
// your codes
}
The great thing about doing it this way, is that your $this->Html->link's will reflect the routes in the paths they generate. (reverse routing)
The routes in cake are quite powerful, you should be able to get some decent friendly urls with them. One extra thing I've used is to use a behaviour - sluggable - to generate a searchable field from the content items title - for pages / content types in the cms.
good luck!

kohana: Remove need for default controller route

I have a route as:
((?<directory>\w+)/?)?((?<controller>\w+)/?)?((?<action>\w+)/?)?((?<id>\d+))?
It works fine but it causes my system to have to include the default controller (index) for all routes to the sub routes. For example, if my page URI is /blog/post (where blog is the directory and post would be the action), my actual URI would have to be blog/index/post - I'd like to be able to fall back to just using blog/post instead.
So, I would like it to be routed to:
directory = blog
controller = index
action = post
Obviously this causes issues when the second parameter is actually a controller. For example directory/controller/action would be routed incorrectly.
Is there a routing method to detect that there are three word parameters, possibly followed by a numeric parameter, which can do what I need?
For claification:
param/param/param(?/id) would be: directory/controller/action(/id)
param/param(?/id) would be: directory/default_controller/action(/id)
i'd actually think that you want to alias blog/index/post with blog/post; insert it as a route before the "catch-all" route that you have; the "one big shoe fits all" approach is not always the best. Especially, if you only have 1 such particular use case.
edit:
"kohana's routing system" is daunting; can't make sense of the elephant they're trying to give birth to there... here are some other suggestions:
Take this issue to the manufacturer; this is definetely an FAQ question
Mess around with the regex patterns. Here's a snippet that might be useful (i put it inside a PHP test case, but you could easily decouple it)
public function testRoutePatterns(){
$data = array(
array(
//most specific: word/word/word/id
'~^(?P<directory>\w+)/(?P<controller>\w+)/(?P<action>\w+)/(?P<id>.*)$~i',
'myModule/blog/post/some-id',
array('directory'=>'myModule', 'controller'=>'blog', 'action'=>'post', 'id'=>'some-id'),
true
),
array(
//less specific: word/word/id
'~^(?P<directory>\w+)/(?P<action>\w+)/(?P<id>.*)$~i',
'blog/post/some-id',
array('directory'=>'blog', 'action'=>'post'), //need to inject "index" controller via "defaults()" here i guess
true
),
);
foreach ($data as $d) {
$matches = array();
list($pattern, $subject, $expected, $bool) = $d;
$actual = (bool) preg_match($pattern, $subject, $matches);
$this->assertEquals($bool, $actual); //assert matching
$this->assertEquals(array(), array_diff($expected, $matches)); //$expected contained in $matches
}
}
As explained on this answer, if you have some route like this:
Route::set('route_name', 'directory/controller/action')
->defaults(array(
'directory' => 'biz',
'controller' => 'foo',
'action' => 'bar',
));
You should have the directory structure like this:
/application/classes/controller/biz/foo.php

Categories