10/10/2018, 09:54

Xin giải pháp về website : Multiple languages

Mong mấy bác chỉ bảo em phương cách làm website đa ngôn ngữ bằng PHP với ạ

Em dùng google thì search ra tùm lum cách nhưng ko biết cách nào là khả thi nhất ,hy vọng nhờ mấy bác đã làm qua những site này cho em biết 1 chút về kinh nghiệm thiết kế database,class và cách bố trí tổ chức file... cho những site thế này luôn
Bác nào biết những tutorial cả tiếng anh và tiếng việt(từ search google) mà mấy bác tâm đắc share cho em luôn nhé,em xin cám ơn.

Và 1 vấn đề nữa em mới viết PHP bằng OOP,thấy phiên bản PHP 5.3 hổ trợ late static binding rất hay.
Code example :
Code:
Class DatabaseObject {
	protected static $table_name;

	// Common Database Methods
	public static function find_all() {
		return static::find_by_sql("SELECT * FROM ".static::$table_name);
  }
  
  	public static function find_by_id($id=0) {
    $result_array =static::find_by_sql("SELECT * FROM ".static::$table_name." WHERE id={$id} LIMIT 1");
		return !empty($result_array) ? array_shift($result_array) : false;
  }
  
  	public static function find_by_sql($sql="") {
    global $database;
    $result_set = $database->query($sql);
    $object_array = array();
    while ($row = $database->fetch_array($result_set)) {
      $object_array[] = static::instantiate($row);
    }
    return $object_array;
  }
	public static function count_all(){
			global $database;
			
			$sql = "select count(*) from ".static::$table_name;
			$result_set = $database->query($sql);
			$row = $database->fetch_array($result_set);
			return array_shift($row);
		}	
		
	private static function instantiate($record) {
	
	$class_name = get_called_class();
    $object = new $class_name;
		// Simple, long-form approach:
		// $object->id 				= $record['id'];
		// $object->username 	= $record['username'];
		// $object->password 	= $record['password'];
		// $object->first_name = $record['first_name'];
		// $object->last_name 	= $record['last_name'];
		
		// More dynamic, short-form approach:
		foreach($record as $attribute=>$value){
		  if($object->has_attribute($attribute)) {
		    $object->$attribute = $value;
		  }
		}
		return $object;
	}
	
	private function has_attribute($attribute) {
	  // We don't care about the value, we just want to know if the key exists
	  // Will return true or false
	  return array_key_exists($attribute, $this->attributes());
	}

	protected function attributes() { 
		// return an array of attribute names and their values
	  $attributes = array();
	  foreach(static::$db_fields as $field) {
	    if(property_exists($this, $field)) {
	      $attributes[$field] = $this->$field;
	    }
	  }
	  return $attributes;
	}
	
	protected function sanitized_attributes() {
	  global $database;
	  $clean_attributes = array();
	  // sanitize the values before submitting
	  // Note: does not alter the actual value of each attribute
	  foreach($this->attributes() as $key => $value){
	    $clean_attributes[$key] = $database->escape_value($value);
	  }
	  return $clean_attributes;
	}
	
	public function save() {
	  // A new record won't have an id yet.
	  return isset($this->id) ? $this->update() : $this->create();
	}
	
	public function create() {
		global $database;
		// Don't forget your SQL syntax and good habits:
		// - INSERT INTO table (key, key) VALUES ('value', 'value')
		// - single-quotes around all values
		// - escape all values to prevent SQL injection
		$attributes = $this->sanitized_attributes();
	  $sql = "INSERT INTO ".static::$table_name." (";
		$sql .= join(", ", array_keys($attributes));
	  $sql .= ") VALUES ('";
		$sql .= join("', '", array_values($attributes));
		$sql .= "')";
	  if($database->query($sql)) {
	    $this->id = $database->insert_id();
	    return true;
	  } else {
	    return false;
	  }
	}

	public function update() {
	  global $database;
		// Don't forget your SQL syntax and good habits:
		// - UPDATE table SET key='value', key='value' WHERE condition
		// - single-quotes around all values
		// - escape all values to prevent SQL injection
		$attributes = $this->sanitized_attributes();
		$attribute_pairs = array();
		foreach($attributes as $key => $value) {
		  $attribute_pairs[] = "{$key}='{$value}'";
		}
		$sql = "UPDATE ".static::$table_name." SET ";
		$sql .= join(", ", $attribute_pairs);
		$sql .= " WHERE id=". $database->escape_value($this->id);
	  $database->query($sql);
	  return ($database->affected_rows() == 1) ? true : false;
	}

	public function delete() {
		global $database;
		// Don't forget your SQL syntax and good habits:
		// - DELETE FROM table WHERE condition LIMIT 1
		// - escape all values to prevent SQL injection
		// - use LIMIT 1
	  $sql = "DELETE FROM ".static::$table_name;
	  $sql .= " WHERE id=". $database->escape_value($this->id);
	  $sql .= " LIMIT 1";
	  $database->query($sql);
	  return ($database->affected_rows() == 1) ? true : false;
	
		// NB: After deleting, the instance of User still 
		// exists, even though the database entry does not.
		// This can be useful, as in:
		//   echo $user->first_name . " was deleted";
		// but, for example, we can't call $user->update() 
		// after calling $user->delete().
	}
	
}
Và các lớp kế thừa
Code:
Class User extends DatabaseObject {
		
		protected static $table_name ="users";
		protected static $db_fields = array('id','username','password','first_name','last_name');
		public $id;
		public $username;
		public $password;
		public $first_name;
		public $last_name;
		
		public function full_name() {
			if(isset($this->first_name) && isset($this->last_name)){
				return $this->first_name."  ".$this->last_name;
			
			} else { return ""; }
		}
		public static function authenticate($username="",$password=""){
			global $database;
			$username= $database->escape_value($username);
			$password= $database->escape_value($password);
			
			$sql="SELECT * FROM users ";
			$sql.= "WHERE username ='{$username}' ";
			$sql.= "AND password ='{$password}' ";
			$sql.= "LIMIT 1";
			$result_array= self::find_by_sql($sql);
			return !empty($result_array)? array_shift($result_array):false;
		}
	
		
	}

--------------------------------------------------------------

class Comment extends DatabaseObject {

  protected static $table_name="comments";
  protected static $db_fields=array('id', 'photograph_id', 'created', 'author', 'body');

  public $id;
  public $photograph_id;
  public $created;
  public $author;
  public $body;

  // "new" is a reserved word so we use "make" (or "build")
	public static function make($photo_id, $author="Anonymous", $body="") {
    if(!empty($photo_id) && !empty($author) && !empty($body)) {
			$comment = new Comment();
	    $comment->photograph_id = (int)$photo_id;
	    $comment->created = strftime("%Y-%m-%d %H:%M:%S", time());
	    $comment->author = $author;
	    $comment->body = $body;
	    return $comment;
		} else {
			return false;
		}
	}
	
	public static function find_comments_on($photo_id=0) {
    global $database;
    $sql = "SELECT * FROM " . self::$table_name;
    $sql .= " WHERE photograph_id=" .$database->escape_value($photo_id);
    $sql .= " ORDER BY created ASC";
    return self::find_by_sql($sql);
	}
	
	
}
------------------------------------------------------------------------

class Photograph extends DatabaseObject {
		protected static $table_name ="photographs";
		protected static $db_fields = array('id','filename','type','size','caption');
		public $id;
		public $filename;
		public $type;
		public $size;
		public $caption;
		
		private $temp_path;
		protected $upload_dir = "images"; 
		public $errors = array();
		
		protected $upload_errors = array(
		// http://www.php.net/manual/en/features.file-upload.errors.php
		  UPLOAD_ERR_OK 				=> "No errors.",
		  UPLOAD_ERR_INI_SIZE  	=> "Larger than upload_max_filesize.",
		  UPLOAD_ERR_FORM_SIZE 	=> "Larger than form MAX_FILE_SIZE.",
		  UPLOAD_ERR_PARTIAL 		=> "Partial upload.",
		  UPLOAD_ERR_NO_FILE 		=> "No file.",
		  UPLOAD_ERR_NO_TMP_DIR => "No temporary directory.",
		  UPLOAD_ERR_CANT_WRITE => "Can't write to disk.",
		  UPLOAD_ERR_EXTENSION 	=> "File upload stopped by extension."
			);

	// Pass in $_FILE(['uploaded_file']) as an argument
  		public function attach_file($file) {
		// Perform error checking on the form parameters
			if(!$file || empty($file) || !is_array($file)) {
		  // error: nothing uploaded or wrong argument usage
			  $this->errors[] = "No file was uploaded.";
			  return false;
			} elseif($file['error'] != 0) {
			  // error: report what PHP says went wrong
			  $this->errors[] = $this->upload_errors[$file['error']];
			  return false;
			} else {
				// Set object attributes to the form parameters.
			  $this->temp_path  = $file['tmp_name'];
			  $this->filename   = basename($file['name']);
			  $this->type       = $file['type'];
			  $this->size       = $file['size'];
			// Don't worry about saving anything to the database yet.
			return true;
			}
		}
			//Override method
		public function save() {
			// A new record won't have an id yet.
			if(isset($this->id)) {
				// Really just to update the caption
				$this->update();
			} else {
				// Make sure there are no errors
				
				// Can't save if there are pre-existing errors
			  if(!empty($this->errors)) { return false; }
			  
				// Make sure the caption is not too long for the DB
			  if(strlen($this->caption) > 255) {
					$this->errors[] = "The caption can only be 255 characters long.";
					return false;
				}
			
			  // Can't save without filename and temp location
			  if(empty($this->filename) || empty($this->temp_path)) {
			    $this->errors[] = "The file location was not available.";
			    return false;
			  }
				
				// Determine the target_path
			  $target_path = x_public. $this->upload_dir .DS. $this->filename;
			  
			  // Make sure a file doesn't already exist in the target location
			  if(file_exists($target_path)) {
			    $this->errors[] = "The file {$this->filename} already exists.";
			    return false;
			  }
			
				// Attempt to move the file 
				if(move_uploaded_file($this->temp_path, $target_path)) {
			  	// Success
					// Save a corresponding entry to the database
					if($this->create()) {
						// We are done with temp_path, the file isn't there anymore
						unset($this->temp_path);
						return true;
					}
				} else {
					// File was not moved.
			    $this->errors[] = "The file upload failed, possibly due to incorrect permissions on the upload folder.";
			    return false;
				}
			}
		}
		
		
		public function image_path() {
			return $this->upload_dir.DS.$this->filename;
		}
		public function destroy() {
			if($this->delete()) {
				$target_path = x_public. $this->image_path();
				return unlink($target_path) ? true : false;
			}else {
				return false;
			}
			
		}
		
		public function size_as_text() {
			if($this->size < 1024) {
				return "{$this->size} bytes";
			} elseif($this->size < 1048576) {
				$size_kb = round($this->size/1024);
				return "{$size_kb} KB";
			} else {
				$size_mb = round($this->size/1048576, 1);
				return "{$size_mb} MB";
			}
		}
		public function comments() {
				return Comment::find_comments_on($this->id);
			}
		
}
Và trong OOP có thể override các phương thức khi extends từ lớp cha,vậy chức năng các lớp và phương thức abstract có phải là thừa ko ? vì em ko biết thì khi nào thì nên dùng abstract hay interface,final ...
Mong các bác chỉ bảo thêm.
Thân
thuyduongcd viết 11:59 ngày 10/10/2018
Tạo thư mục lang chứa các file ngôn ngữ (lang/en.php, lang/vi.php, ...)
file lang/en.php
Code:
$T_USERNAME="Username";
$T_PASSWORD="Password";
....
file lang/vi.php
Code:
$T_USERNAME="Tên đăng nhập";
$T_PASSWORD="Mật khẩu";
..........
Thêm đoạn code sau vào đầu trang index.php
Code:
....
if (isset($_GET['lang'])){
    $lang= $_GET['lang']);
    $_SESSION['lang']=$lang;
}elseif (isset($_SESSION['lang'])){
    $lang = $_SESSION['lang'];
}else{
    $lang="vi"; //set default lang
    $_SESSION['lang']="vi";
}
if (file_exists("lang/$lang.php"){ //kiểm tra file lang
   include ("lang/$lang.php");
}else{ // không tồn tại file lang
   $_SESSION['lang']="vi";
   include ("lang/vi.php"); // ngôn ngữ mặc định
}
......
//khi cần hiển thị
echo "$T_USERNAME<br />$T_PASSWORD";
Muốn sử dụng ngôn ngữ nào chỉ cần gọi http://domain/index.php?lang=vi (or ?lang=en)
mabongdem viết 11:54 ngày 10/10/2018
cám ơn bác thuyduongcd nhưng cái em nói đến là kiến trúc và nội dung cả site bác à. ví dụ trang tin tức vừa english vừa vietnamese+chinese+japanese.... lúc đó mới rắc rối.
anhtuannd viết 12:05 ngày 10/10/2018
Giải pháp của mình là database có 2 table:
+ Table tbl_title: chỉ chứa tiêu đề của news và các thứ linh tinh khác gắn với news
+ Table tbl_news: chứa nội dung của news, mỗi entry trong table ở một ngôn ngữ khác nhau. Tbl_news có thể có các field như title_id, news_id, lang_id, content, date, ...
Khi cần hiển thị news gì, ngôn ngữ nào thì chỉ cần select cái title, rồi select news ở ngôn ngữ bạn cần hiển thị tương ứng với cái title ấy.
thuyduongcd viết 11:59 ngày 10/10/2018
Được gửi bởi mabongdem
cám ơn bác thuyduongcd nhưng cái em nói đến là kiến trúc và nội dung cả site bác à. ví dụ trang tin tức vừa english vừa vietnamese+chinese+japanese.... lúc đó mới rắc rối.
Có gì mà rắc rối. Mỗi trang đều thêm đoạn code đó vào đầu (thường thì gắn nó vào header).
ngôn ngữ được lưu vào session nên những trang sau tự động dùng ngôn ngữ đó. Bất cứ khi nào cần chuyển sang ngôn ngữ khác chỉ cần thêm &lang="xx" vào cuối URL là được.
s.code viết 12:03 ngày 10/10/2018
Chú ý: Đa ngôn ngữ có 2 kiểu:

1. Đa ngôn ngữ về giao diện (tĩnh)
2. Đã ngôn ngữ về nội dung (động)

cái 1 thì dễ và nhiều site đã làm. Đã là sử dụng các file language khác nhau.

Cái thứ 2 nó bao gồm cả cái 1 và đa ngôn ngữ về nội dung (song ngữ). Mỗi một trang đều có nội dung cho từng ngôn ngữ.

Riêng cái thứ 2 muốn good thì người lên kiến trúc dự án (core) phải tính trước. Khi một module nào đó được viết xong và đóng gói. Thì số nội dung theo từng ngôn ngữ cũng tương đối.

Một ví dụ:

Tôi đã xây dựng các module sau: product, news, about, contact

Trong trường hợp 1: Muốn tạo 1 site chỉ có 1 ngôn ngữ (Tiếng việt)
Trường hợp 2: muốn tạo 1 site có 2 ngôn ngữ (Việt, Anh)
Trường hợp 3: muốn tạo 1 site có 4 ngôn ngữ (Việt, Anh, Nhật)

Nếu mà thiết kế không tốt thì suốt ngày viết đi viết lại. Khi lập trình một module bất kỳ ta nên tham chiếu đến số ngôn ngữ đã được định nghĩa.

Cũng ví dụ trên. Sau khi site đã code xong hoàn toàn. Tôi chỉ cần vào phần quản lý ngôn ngữ add thêm 1 ngôn ngữ mới. Thì tất cả các module trong dự án. Phải tự refrer đến và cho nhập (quản lý), xuất (hiển thị) theo số ngôn ngữ mà thằng core quy định.

Giả sử có 1 dự án. Ban đầu chỉ có 1 ngôn ngữ nhưng sau 1 thời gian vì nhu cầu mà cần phải thêm 1,2,3... ngôn ngữ mới. LÚc này mà code lại hoặc thiết kế DB lại thì coi như điên luôn.

Nếu ng lập trình thiết kế mô hình DB tốt. Thiết việc thêm bớt ngôn ngữ chỉ là vấn đề quản trị mà rất ít liên quan đến code, DB.
Thanh duc viết 12:07 ngày 10/10/2018
Nhìn code mà yêu ghê
chesterben viết 12:06 ngày 10/10/2018
Muốn làm nội dung đa ngôn ngữ không khó. Mình không phải là PHP Developer nhưng hy vọng giúp bạn được tí chút. Mình xin lấy một ví dụ cụ thể:

Models:
Entry
  • title
  • slug
  • content
  • pub_date
  • languages (ForeignKey -> Language)

Language
  • name
  • short_name
  • desc
  • is_default


Như vậy khi gửi nội dung bạn phải chọn ngôn ngữ cho bài post đó. Việc còn lại là ở lớp Views, bạn sẽ xử lý để hiển thị cho đúng ngôn ngữ mà người dùng chọn lựa.

Chúc thành công !
mabongdem viết 12:09 ngày 10/10/2018
up phát,mọi người tiếp tục thảo luận nào...
thuyduongcd viết 11:55 ngày 10/10/2018
Rõ ràng rồi còn gì. Tùy theo mục đích mà áp dụng.
Nếu chỉ đa ngôn ngữ về giao diện thì làm theo mình hướng dẫn.
Nếu đa ngôn ngữ về nội dung (tin tiếng anh riêng tin tiếng Việt riêng) thì thêm trường lang cho mỗi mẫu tin. Tùy theo lang nào mà truy vấn nó ra.
Bài liên quan
0