MySQL/PHP Multilanguage form - php

How can I build multilanguage form with Translations from database using pdo object?
for example, there is my code to create pdo object:
try {
$pdo = new PDO("mysql:host=$servername;dbname=$dbname",$username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo $e->getMEssage();
die();
}
$sql = "SELECT FROM `lang_content` `id`, `:lang`";
$query = $pdo->prepare($sql);
if($query->execute(array(
':lang' => $_GET['lang'].'_content'
))){
// Show content
}
MySQL table structure:
CREATE TABLE `lang_content` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`en_content` VARCHAR(1000) NULL DEFAULT NULL,
`lv_content` VARCHAR(1000) NULL DEFAULT NULL,
`ru_content` VARCHAR(1000) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
COMMENT='All translations'
ENGINE=MyISAM
;
MySQL table data example:
id en_content lv_content ru_content
1 MOD KASKO КАСКО
2 MTPL OCTA ОСАГО
And I would like to use it like:
if lang en
<?= $result->1 ?> // Echo MOD
Will return MOD,
<?= $result->2 ?> // Echo MTPL
Will return MTPL

Honestly that is not good practice to do your translation via database. Many big platforms do it through some sort of text format. For example magento does it through csv files.
You could start your development with the following code.
Your Spanish translation file es.csv
'Home', 'Inicio'
'Page 1', 'Pagina 1'
'Page 2', 'Pagina 2'
'Page 3', 'Pagina 3'
The first column of words would be your default language which would be defined within a function.
<a href="#"><?php __("Home"); ?>
Then you would use a switch statement like the one you have on top to capture which language the user choose. The __() function would look something like this. Where input would be "home"
$csv = array_map('str_getcsv', file($file));
// Logic (where it would do the translation)
// It could look for the input in the $cvs variable if it exist then use the next value of the current array. Else keep the text the same.
Your cvs file would need to make the exact words to be translated.

Related

cakephp multilingual dynamic content from database

I want to develop website with option to select language
at the time I do not know about how to structure my database tables i.e
either I should add separate fields for each language e.g
tbl_posts
id, title_en,title_fr,description_en,description_fr,....
or should I get help of google translate at run time
or there is something else easy to do this
secondly I will need to have URLs like
www.domain.com/en/posts/ & www.domain.com/fr/posts/
third what other things should I keep in mind to develop multilingual website.
looking for standardized, more optimized, easy manageable and fully dynamic solution.
Cakephp
Step 1: In your lib/Cake/basic.php add function
if (!function_exists('__dbt')) {
function __dbt($text, $args = null) {
if($text==null){
return null;
}else{
$languageUse = Configure::read('Config.language');
if($languageUse!='en-us' && $languageUse!='eng'){
$modelName = ucfirst($languageUse)."Translation";
$model = ClassRegistry::init($modelName);
$data = $model->find('first',array('fields'=>array('translation'),'conditions'=>array("text"=>"$text")));
if(!empty($data[$modelName]) && $data[$modelName]['translation']!=''){
return $data[$modelName]['translation'];//die('1');
}else{
// Please copy & paste below code from your basic.php __() function
App::uses('I18n', 'I18n');
$translated = I18n::translate($text);
$arguments = func_get_args();
return I18n::insertArgs($translated, array_slice($arguments, 1));
}
}else{
// Please copy & paste below code from your basic.php __() function
App::uses('I18n', 'I18n');
$translated = I18n::translate($text);
$arguments = func_get_args();
return I18n::insertArgs($translated, array_slice($arguments, 1));
}
}
}
}
Step 2: create table on basis of language you want to use
Note: table name should be prefix locale + _tranlations
example: hin_translations, tha_translations etc.
CREATE TABLE IF NOT EXISTS `hin_translations` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`text` text NOT NULL,
`translation` text NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
In above table add english string in your text column and its translation in translation column.
Step 3: where ever you want to change language string either from database or locale po file just use
__dbt("Write your string here");
:) enjoy your multilingual site

Delete mysql records is not working in codeIgniter

This is my controller function:
function delete_article($title){
if ($this->session->userdata('User') && $this->session->userdata('User') == 'admin#example.com') {
$this->load->model('Article', 'Article', TRUE);
$this->Article->delete_article_db($title);
redirect('admin/user_article');
} else {
redirect('');
}
}
And this is my model function for deleting the database record:
function delete_article_db($title) {
$this->db->where('Title', $title);
$this->db->delete('article');
}
When I run this code, nothing gets deleted. However, the code does not fire any errors or warnings.
This is my MySQL table structure:
CREATE TABLE IF NOT EXISTS `article` (
`Name` text NOT NULL,
`Email` text NOT NULL,
`Phone` text NOT NULL,
`Address` text NOT NULL,
`Literature` text NOT NULL,
`Title` text NOT NULL,
`Submission_Name` text NOT NULL,
`Additional_Name` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
A bit of a wild guess, but looking at your controller method, and the variable names, I'm assuming you're passing the title via url, something like
http://example.com/admin/delete/Title to be deleted
Which leads me to think your query is not working because of encoding of the spaces in the url (or other characters) which won't match the not encoded spaces in your db.
Try with:
function delete_article_db($title) {
$this->db->where('Title', rawurldecode($title) );
$this->db->delete('article');
}

Yii INSERT ... ON DUPLICATE UPDATE

I am working on a Yii project. How can I use the ON DUPLICATE feature of MySQL ( http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html ) when doing a save() on a Yii model?
My MySQL is as follows:
CREATE TABLE `ck_space_calendar_cache` (
`space_id` int(11) NOT NULL,
`day` date NOT NULL,
`available` tinyint(1) unsigned NOT NULL DEFAULT '0',
`price` decimal(12,2) DEFAULT NULL,
`offer` varchar(45) DEFAULT NULL,
`presale_date` date DEFAULT NULL,
`presale_price` decimal(12,2) DEFAULT NULL,
`value_x` int(11) DEFAULT NULL,
`value_y` int(11) DEFAULT NULL,
PRIMARY KEY (`space_id`,`day`),
KEY `space` (`space_id`),
CONSTRAINT `space` FOREIGN KEY (`space_id`) REFERENCES `ck_space` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
My PHP is a follows:
$cache = new SpaceCalendarCache();
$cache->attributes = $day; //Some array with attributes
$cache->save();
If there is a duplicate in my primary key (sapce_id,day), I don't want it to complain, I just want it to update with the latest data.
I know how to do it in raw SQL, I was just wondering if there is a clean Yii way to do it.
You are using models in Yii, its quite simple .. try to load you model where you suspect to have duplicate entries, if you find the entry the model is loaded else null is return. now if your model is null simply create new model. rest is your normal code to insert a new record.
//try to load model with available id i.e. unique key
$model = someModel::model()->findByPk($id);
//now check if the model is null
if(!$model) $model = new someModel();
//Apply you new changes
$model->attributes = $attributes;
//save
$model->save();
Refer to post controllers update method in sample app Yii blog. I might be wrong with spelling of function names, sorry for that.
I'm repeating two main points from previous answers I think you should keep:
Don't (try to) use "on duplicate key update" since its MySQL-only, as txyoji points out.
Prefer the select->if not found then insert->else insert demonstrated by Uday Sawant.
There's another point here, though: Concurrency. Although for low traffic applications the probability that you'll get in trouble is minimal (still never zero), I think we always be careful about this.
From a transactional point of view, "INSERT .. ON DUPLICATE UPDATE" is not equivalent to selecting into your application's memory and then inserting or updating. The first is a single transaction, then second is not.
Here's a bad scenario:
You do select your record using findByPk() which returns null
Some other transaction (from some other user request) inserts a record with the id you just failed to select
At the next instant you try to insert it again
In this case you'll either get an exception (if you're working with a unique key, as you do here) or a duplicate entry. Duplicate entries are much harder to pick up (usually nothing seems weird until your users see duplicate records).
The solution here is to set a strict isolation level, for example "serializable", and then begin a transaction.
Here's an example for yii:
Yii::app()->db->createCommand('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE');
$trn = Yii::app()->db->beginTransaction();
try {
// Try to load model with available id i.e. unique key
// Since we're in serializable isolation level, even if
// the record does not exist the RDBMS will lock this key
// so nobody can insert it until you commit.
// The same shold for the (most usual) case of findByAttributes()
$model = someModel::model()->findByAttributes(array(
'sapce_id' => $sapceId,
'day' => $day
));
//now check if the model is null
if (!$model) {
$model = new someModel();
}
//Apply you new changes
$model->attributes = $attributes;
//save
$model->save();
// Commit changes
$trn->commit();
} catch (Exception $e) {
// Rollback transaction
$trn->rollback();
echo $e->getMessage();
}
You can see more about isolation levels at least in the following links and see what every isolation level has to offer in data integrity in exchange for concurrency
http://technet.microsoft.com/en-us/library/ms173763.aspx
http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
I overrode beforeValidate() where I checked if a duplicate exists. If one does, I set $this->setIsNewRecord(false);
Seems to work. Not sure how performant it is.
The "On Duplicate Key Update" feature is specific to MySQL's dialect of SQL. Its unlikely to be implemented in any data abstraction layer. ZendDB and Propel don't have an equivalent.
You can simulate the behavior by attempting an insert in a try/catch and update if insert fails with the proper error code. (duplicate key error).
I agree with #txyoji's analysis of the problem, but I would use a different solution.
You can extend the save() method of the model to look for an existing record, and update it, or insert a new row if it doesn't.
you have to use try catch like that:
try{
$model->save();
}
catch(CDbException $e){
$model->isNewRecord = false;
$model->save();
}
Ooops, sorry.. this answer for yii2
If you dont use yii model, this function generates mysql syntax insert on duplicates key update
static function insertDuplicate($table, $columns, $duplicates, $values="",$ignores=false){
$params=array();
$names=array();
$tipe="VALUES";
$ignore=($ignores===true)?"IGNORE":"";
$placeholders=array();
if(is_array($columns)){
if(!isset($columns[0])){
foreach($columns as $name=>$value)
{
$names[]=$name;
if($value instanceof CDbExpression)
{
$placeholders[] = $value->expression;
foreach($value->params as $n => $v)
$params[$n] = $v;
}
else
{
$placeholders[] = ':' . $name;
$params[':' . $name] = $value;
}
}
}else{
$names=$columns;
}
$myColumn=implode(', ',$names);
if($values!=""){
$myValue=$values;
}else{
$myValue='('.implode(', ', $placeholders).')';
}
}else{
$myColumn=$columns;
$myValue=$values;
}
if($values!=""){
if(substr(strtoupper($values),0,6)=="SELECT"){
$tipe="";
}
}
$d = array();
if(is_array($duplicates)){
if(!isset($duplicates[0])){
foreach($duplicates as $duplicate=>$act)
{
if($act=="increase"){
$dup=$table.".".$duplicate . ' = '.$table.".".$duplicate.' + VALUES('.$duplicate.')';
}elseif($act=="decrease"){
$dup=$table.".".$duplicate . ' = '.$table.".".$duplicate.' - VALUES('.$duplicate.')';
}else{
$dup=$table.".".$duplicate . ' = VALUES('.$duplicate.')';
}
$d[] = $dup;
}
}else{
foreach($duplicates as $duplicate){
$dup=$duplicate . ' = VALUES('.$duplicate.')';
$d[] = $dup;
}
}
$myDuplicate= implode(', ', $d);
}else{
$myDuplicate=$duplicates;
}
$sql='INSERT '.$ignore.' INTO ' . $table
. ' (' . $myColumn . ') '.$tipe.' '
. $myValue . ' ON DUPLICATE KEY UPDATE ' .$myDuplicate;
return Yii::$app->db->createCommand($sql)->bindValues($params)->execute();
}
Place that function into someclass, and dont forget use
use yii\db\Command;
in that class
That function can insert on key update, update increment, update decrement, update multi from a value, and update from select
Usage :
//to update available=1 and price into 100
someclass::insertDuplicate(
'ck_space_calendar_cache',
['sapce_id'=>1,'day'=>'2022-09-01','available'=>1,'price'=>100],
['available','price']
);
//to update price increase by 100, (if price is decrease then change it to decrease)
someclass::insertDuplicate(
'ck_space_calendar_cache',
['sapce_id'=>1,'day'=>'2022-09-01','price'=>100],
['price'=>'increase']
);
//to update mass with a value
someclass::insertDuplicate(
'ck_space_calendar_cache',
['sapce_id','day','price'],
['price'],
'(1,'2022-09-01',100),(2,'2022-09-01',300),(3,'2022-09-01',100)'
);
//to update mass with select from another table
someclass::insertDuplicate(
'ck_space_calendar_cache',
['sapce_id','day','price'],
['price'],
'SELECT otherid as sapce_id, otherday as day, otherprice as price from other WHERE otherprice>100'
);

External linking and cakephp's routing engine (multilingual)

I recently build a tiny routing 'extension', that calls the routes from a MySQL table (structure downwards). I think it's worth to be mentioned, that this page runs in multiple languages (German and English). So - relying on the cookie, that's currently set in the client's browser - the corresponding routings get connected.
The problem is, that if the user cannot (externally) be linked to a german content page, if his browser's language cookie was set to the English language (because the english routes got connected).
Does anyone got a proper solution for this? To be honest, I don't really know, how to programmatically extend the Router-class' functionality.
The MySQL table's structure looks like this:
CREATE TABLE `routes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`language` varchar(5) COLLATE latin1_general_ci NOT NULL DEFAULT 'de',
`route` varchar(64) COLLATE latin1_general_ci NOT NULL,
`controller` varchar(64) COLLATE latin1_general_ci NOT NULL,
`action` varchar(64) COLLATE latin1_general_ci NOT NULL,
PRIMARY KEY (`id`)
)
Use p28n - http://bakery.cakephp.org/articles/view/p28n-the-top-to-bottom-persistent-internationalization-tutorial
It works well, I use it a lot and it's part of my standard build now.
I also have a script that will return translated urls depending on the chosen language. I can't remember where I found the script it's based on, but if it helps I can try to send you a clean version.
EDIT: Okay, here's the bones:
This script will translate urls so that they remain SEO friendly across languages. The language switching and message translation is handled by p28n (above) and po.
Put this line in your app/config/bootstrap.php file:
include_once("translate.php");
Put this as the first line of code in app/config/routes.php:
$fromUrl = translate($fromUrl,true);
Now you need to create the file app/config/translate.php which contains all of the routing information:
function translate($str = null,$total = false)
{
// If this is an RSS route, ignore it and bounce straight out
if (strpos($str,".rss")!==false)
return $str;
$translatetable = array(
'some-url-in-german' => array('/articles/msome-url-in-german',1),
'some-url-in-english' => array('/articles/some-url-in-german',2),
'a-german-article' => array('/posts/a-german-article',1),
'an-english-article' => array('/posts/a-german-article',2)
);
if($str)
{
if($total)
{
$old = explode('/',$str);
$lastone = end($old);
if(empty($lastone)) array_pop($old);
$new = array();
/* translate each part or leave untranslated part */
for($i = 0 ; $i <sizeof($old) ; $i++)
{
$new[$i] = translate($old[$i]);
}
/* construct the translated url. This also adds
a trailing "/" even if it wasn't in the original */
$new_url="";
foreach($new as $n)
{
$new_url .= $n."/";
}
return $new_url;
}
else
{
foreach ($translatetable as $orig => $new)
{
if($str == $orig)
{
$str = $new[0];
switchLanguage($new[1]);
}
}
return $str;
}
}
}
function switchLanguage($lang)
{
if($lang>0)
{
$translatetable = array(
'1' => 'de',
'2'=> 'eng'
);
Configure::write(array('Config.language'=>$translatetable[$lang]));
}
}
It's quite straightforward really - the trick is getting it to feed into CakePHP at the right places. I hope it is of some use to you.

(PHP & mySQL) Treat rows as columns

I am working on PHP & mySQL based website. I am trying to setup the 'Settings' page in administration panel where I can enter different settings to manage the site. The settings can range upto 100 (or even more) depending upon the requirements. So instead of making 100 columns (and increase if I have to add more columns in future), I want to store the data in row wise format and fetch the values as if I am fetching it from columns.
REFERENCE:
I found a similar real life implementation of such feature in the most popular blogging tool 'Wordpress'. For reference, it is the 'wp_options' table that I am talking about.(Please correct me I am wrong)
EXAMPLE:
Here's a quick example of what (& why) I am trying to do it that way:
--Table settings
P.KEY option_name option_value
1 site_name XYZ site inc.
2 siteurl http://www.xyz.com
3 slogan Welcome to my XYZ site
4 admin_email admin#xyz.com
5 mailserver_url mail.xyz.com
6 mailserver_port 23
..... etc.
As you can see from above, I have listed very few options and they are increasing in number. (Just for the records, my installation of Wordpress has 902 rows in wp_options table and I did not see any duplicate option_name). So I have the feeling that I am well off if I apply the same working principle as Wordpress to accomodate growth of the settings. Also I want to do it so that once I save all the settings in DB, I want to retrieve all the settings and populate the respective fields in the form, for which the entries exist in DB.
ONE OF MY CODE TRIALS:
--
-- Table structure for table `settings`
--
CREATE TABLE IF NOT EXISTS `settings` (
`set_id` tinyint(3) NOT NULL auto_increment,
`option_name` varchar(255) NOT NULL,
`option_value` varchar(255) NOT NULL,
PRIMARY KEY (`set_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
--
-- Dumping data for table `settings`
--
INSERT INTO `settings` (`set_id`, `option_name`, `option_value`) VALUES
(1, 'site_name', 'XYZ site inc.'),
(2, 'slogan', 'Welcome to my XYZ site');
$result = mysql_query("SELECT option_name, option_value FROM settings");
$defaults = array('option_name', 'option_value');
while( list($n, $v) = mysql_fetch_array($result) )
{
$defaults['option_name'] .= $n;
$defaults['option_value'] .= $v;
}
echo $defaults['option_name'].'---'.$defaults['option_value'].'<br />';
//The above code gives me the following Output:
//site_nameslogan---XYZ site inc.Welcome to my XYZ site
When I run the above query, I also receive 2 PHP Notices that says:
Undefined index: option_name
Undefined index: option_value
I would appreciate any replies that could show me the PHP code to retrieve the options successfully and eliminate the Undefined index issues as well. Also, like I mentioned earlier, I want to retrieve all the existing settings and populate the respective fields in the form when I visit the settings page next, after storing the data.
Thanks fly out to all in advance.
PHP gives you warning because $defaults['option_name'] and $defaults['option_value'] are not being initialized before they are used in .= operation.
So just put
$defaults['option_name'] = '';
$defaults['option_value'] = '';
before the loop and warning will go away.
The rest of the code is completely correct, although you don't have to have set_id column at all since every setting will have unique name, that name (option_name column) can be used as primary key.
Another thing that you can improve your code, is to use $defaults differently, like so
$defaults[$n] = $v;
Then you can use every setting on its own without looking through two huge strings.
$site_url = $defaults['site_url'];
foreach ($defaults as $name => $value) {
echo $name, ' = ', $value, '<br>';
}
This should do the trick:
$defaults = array('option_name' => array( ), 'option_value' => array( ) );
while( list($n, $v) = mysql_fetch_array($result) )
{
$defaults['option_name'][] = $n;
$defaults['option_value'][] = $v;
}
Then in your view iterate over $defaults['option_name'] and $defaults['option_value']

Categories