การใช้งานหลายภาษากับ Phalcon php

ก่อนอื่น สำหรับคนที่ยังไม่ได้ติดตั้ง Phalcon php หรือติดตั้งไม่ได้ ให้ลองทำตามวิธีในหน้าติดตั้ง Phalcon บน Windows ก่อนนะครับ

โครงสร้าง folder ที่เราจะใช้สำหรับไฟล์ภาษาและไฟล์ที่จะทำงานแปลภาษา จะเป็นดังนี้

 app
     config
         config.php
         loader.php
     controllers
         ControllerBase.php
      extend
          Phalcon
              Mvc
                  Url.php
      language
          en
             index.php
             main.php
         th
             index.php
             main.php
     libraries
             Lang.php

ให้เราเริ่มต้นเข้าไปแก้ไข/เพิ่มเติมในไฟล์ต่างๆไล่ตามดังต่อไปนี้

app/config/config.php


    'application' => array(
        'controllersDir' => APPFULLPATH . '/controllers/',
        'modelsDir' => APPFULLPATH . '/models/',
        'viewsDir' => APPFULLPATH . '/views/',
        'libraryDir' => APPFULLPATH . '/libraries/',
        'extendDir' => APPFULLPATH . '/extend/',
        'cacheDir' => APPFULLPATH . '/cache/',
        'languageDir' => APPFULLPATH.'/language/',
    ),
    'language' => array(
        'defaultLang' => 'th',
        'availableLang' => array(
            'en' => array('name' => 'English', 'locale' => 'en'),
            'th' => array('name' => 'ไทย', 'locale' => 'th'),
        ),
        'fallbackLang' => 'en',
    ),

จากโค้ดข้างบน คือ ให้เพิ่ม languageDir, extendDir และ libraryDir เข้าไปใน application array. ส่วนค่า APPFULLPATH ถ้าหากไม่ได้กำหนดไว้เองในหน้า index.php ก็ให้ใช้อ้างอิงจากแบบเดิมที่ Phalcon สร้างให้ตอนสั่ง phalcon create-project

app/config/loader.php


$loader->registerDirs(
    array(
        $config->application->controllersDir,
        $config->application->modelsDir,
        $config->application->libraryDir,
        $config->application->extendDir,
    )
)->register();

$loader->registerNamespaces(
    array(
        'Libraries' => $config->application->libraryDir,
        'Extend' => $config->application->extendDir,
    )
)->register();

ทำการลงทะเบียนโฟลเดอร์สำหรับ libraryDir และ extendDir เข้าไปให้ระบบมันรู้จักในการใช้ auto load. จากนั้นทำการลงทะเบียน namespace ให้เรียกใช้ได้ถูก folder (directory).
สำหรับ extend dir คือโฟลเดอร์ที่เอาไว้สำหรับเก็บส่วนขยายที่ทำต่อจาก Phalcon อีกทีครับ แยกเป็นสัดส่วนเพื่อให้เป็นระบบ ดูง่ายกว่า.

app/controllers/ControllerBase.php


use \Phalcon\Mvc\Controller;

class ControllerBase extends Controller
{


    public $lang;
    private $lang_loaded;
    protected  function langLoad($lang_file = '')
    {
        $lang = new \Libraries\Lang();
        $lang->dispatcher = $this->dispatcher;
        $lang->setLoaded($this->lang_loaded);

        $translate = $lang->load($lang_file);

        $this->lang_loaded = $lang->getLoaded();
        $this->view->setVar('t', $translate);

        $this->lang = $lang;
        unset($lang, $translate);
    }// langLoad

    protected function beforeExecuteRoute($dispatcher)
    {
        $no_redirect_exts = ['css', 'js', 'gif', 'jpg', 'jpeg', 'png'];
        preg_match('#/[a-z]{2}/# iu', $this->router->getRewriteUri(), $matched_lang);
        if ($dispatcher->getParam('lang') == null && count($matched_lang) <= 0) {
            if (strpos($this->router->getRewriteUri(), '.') !== false) {
                $url_exp = explode('.', $this->router->getRewriteUri());
                if (
                    is_array($url_exp) && 
                    array_key_exists(count($url_exp)-1, $url_exp) && 
                    in_array(strtolower($url_exp[count($url_exp)-1]), $no_redirect_exts)
                ) {
                    $no_redirect = true;
                }
            }
            
            if (!isset($no_redirect) || (isset($no_redirect) && $no_redirect === false)) {
                $this->response->redirect($this->router->getRewriteUri());
                unset($config, $current_lang);
            }
        }
        unset($matched_lang, $no_redirect, $no_redirect_exts, $url_exp);
    }// beforeExecuteRoute

}


สำหรับใน base controller นี้มีเพิ่ม 2 ส่วนหลักๆ คือ beforeExecuteRoute สำหรับ redirect URL เวลาที่ไม่มี URI ภาษามาด้วย เช่น เราเรียก http://localhost/phalcon ซึ่งมันไม่มี URI ภาษา จึงต้อง redirect ไป http://localhost/phalcon/th
ส่วน langLoad จะเอาไว้ใช้เรียกโหลดไฟล์ภาษาครับ.

app/extend/Phalcon/Mvc/Url.php


namespace Extend\Phalcon\Mvc;

class Url extends \Phalcon\Mvc\Url
{


    /**
    * Generates a URL
    *
    *<code>
    *
    * //Generate a URL appending the URI to the base URI
    * echo $url->get('products/edit/1');
    *
    * //Generate a URL for a predefined route
    * echo $url->get(array('for' => 'blog-post', 'title' => 'some-cool-stuff', 'year' => '2012'));
    *
    *</code>
    *
    * @param string|array $uri
    * @param array|object args Optional arguments to be appended to the query string
    * @param bool|null $local
    * @return string
    */
    public function get($uri=null, $args=null, $local=null)
    {
        // get the value from parent class.
        $got_uris = parent::get($uri, $args, $local);
        
        // remove double slash and return.
        if (strpos($got_uris, '://') === false) {
            return str_replace('//', '/', $got_uris);
        } else {
            return $got_uris;
        }
    }// get


    /**
     * Returns the prefix for all the generated urls. By default /
     *
     * @return string
     */
    public function getBaseUri($with_lang = true)
    {
        //$disp = new \Phalcon\Mvc\Dispatcher();
        $dispatcher = $this->_dependencyInjector->getShared('dispatcher');
        if ($dispatcher->getParam('lang') == null) {
            $config = $this->_dependencyInjector->getShared('config');
            $current_lang = $config->language->defaultLang;
        } else {
            $current_lang = $dispatcher->getParam('lang');
        }
        unset($dispatcher, $config);
        
        if ($with_lang === true) {
            return parent::getBaseUri().$current_lang.'/';
        } else {
            return parent::getBaseUri();
        }
    }// getBaseUri


    /**
     * get current uri.
     * 
     * @return string example: /installed-path/{lang}/current-controller/current-action
     */
    public function getCurrentUri()
    {
        $router = $this->_dependencyInjector->getShared('router');
        $dispatcher = $this->_dependencyInjector->getShared('dispatcher');
        
        return $this->getBaseUri().str_replace('/'.$dispatcher->getParam('lang').'/', '', $router->getRewriteUri());
    }// getCurrentUri


    /**
     * get current uri from base uri.
     * 
     * @param boolean $with_lang
     * @return string example: /{lang}/current-controller/current-action
     */
    public function getCurrentUriFromBase($with_lang = true)
    {
        $router = $this->_dependencyInjector->getShared('router');
        $dispatcher = $this->_dependencyInjector->getShared('dispatcher');
        
        if ($with_lang === true) {
            return $router->getRewriteUri();
        } else {
            return str_replace('/'.$dispatcher->getParam('lang'), '', $router->getRewriteUri());
        }
    }// getCurrentUriFromBase


    /**
     * get current url with new language.
     * 
     * @param string $lang_name
     * @return string example: http://domain.tld/installed-path/{lang}/current-controller/current-action
     */
    public function getCurrentUrlNewLanguage($lang_name = '')
    {
        $request = new \Phalcon\Http\Request();
        return $request->getScheme().'://'.$request->getServer('SERVER_NAME').$this->getBaseUri(false).$lang_name.$this->getCurrentUriFromBase(false);
    }// getCurrentUrlNewLanguage


}

ไฟล์นี้มีไว้เพื่อสร้าง URL ใหม่ที่มี URI ภาษาห้อยติ่งติดมาด้วยดังที่ยกตัวอย่างไปแล้วด้านบน. หากขาดไฟล์นี้การ redirect ไปยัง URL ที่มีภาษาจะเป็นการ redirect loop นะครับ!

นอกจากเพิ่ม URI ภาษาห้อยติ่งแล้วยังเพิ่ม method ใหม่เอาไว้สร้าง URL สำหรับเปลี่ยนภาษาด้วย นั่นคือ getCurrentUrlNewLanguage ลองใช้กันดู

app/language/th/index.php

อันนี้เป็นไฟล์ภาษาธรรมดา ก็ไม่มีอะไรต่างจากในเอกสารของ Phalcon นัก เพียงแต่แยกย่อยออกมาให้มันรองรับได้เยอะขึ้นโดยไม่ต้องยัดทั้งหมดลงไปที่ไฟล์เดียวเท่านั้นแหละครับ.


$messages = [
    'index_welcome' => 'ยินดีต้อนรับ.',
    'index_hello_X' => 'สวัสดี %name%',
];

app/libraries/Lang.php


namespace Libraries;

class Lang
{


    public $dispatcher;
    private $lang;
    private $lang_loaded;


    /**
     * get the translated language from language array key.
     * 
     * @param string $message language array key
     * @param array $params replacable array key => value
     * @return string translated string
     */
    public function get($message = '', $params = null)
    {
        return $this->lang->_($message, $params);
    }// get


    /**
     * get loaded messages
     * 
     * @return array
     */
    public function getLoaded()
    {
      return $this->lang_loaded;  
    }// getLoaded


    /**
     * load language file. the language file construction is app/language/{lang}/{lang_file}.php
     * 
     * @global object $config global configuration
     * @param string $lang_file the language file
     */
    public function load($lang_file = '')
    {
        global $config;
        
        // get current language
        $lang_uri = $this->dispatcher->getParam('lang');
        if ($lang_uri == null) {
            $lang_uri = $config->language->fallbackLang;
        }
        
        // get language dir and translated file.
        if (strpos($lang_file, '::') === false) {
            $language_path = $config->application->languageDir;
        } else {
            $lang_exp = explode('::', $lang_file);
            $module_name = $lang_exp[0];
            $lang_file = $lang_exp[1];
            if ($module_name == 'core') {
                $language_path = $config->application->languageDir;
            } else {
                $language_path = MODULEFULLPATH.'/'.$module_name.'/language/';
            }
            
            unset($lang_exp, $module_name);
        }
        
        if (file_exists($language_path.$lang_uri.'/'.$lang_file.'.php')) {
            require $language_path.$lang_uri.'/'.$lang_file.'.php';
        } else {
            require $language_path.$config->language->fallbackLang.'/'.$lang_file.'.php';
        }
        
        // load messages into array before call to translate adapter.
        if (isset($messages)) {
            if (empty($this->lang_loaded)) {
                $this->lang_loaded = $messages;
            } else {
                $this->lang_loaded = array_merge($this->lang_loaded, $messages);
            }
        }
        
        // get language content
        $translate = new \Phalcon\Translate\Adapter\NativeArray(array(
            'content' => $this->lang_loaded
        ));
        
        $this->lang = $translate;
        
        unset($lang_file, $lang_uri, $language_path);
        return $translate;
    }// load


    /**
     * set loaded messages.
     * 
     * @param array $lang_loaded
     */
    public function setLoaded($lang_loaded)
    {
        $this->lang_loaded = $lang_loaded;
    }// setLoaded


}

อันนี้เป็นไฟล์ที่ทำงานเกี่ยวกับภาษาล้วนๆแท้ๆเลยครับ

การเรียกใช้งาน

จากภายใน controller ถ้าเราต้องการเรียกไฟล์ภาษาที่ชื่อ index.php เราก็เขียนแค่ $this->langLoad('index');

สำหรับการแปลภาษาจาก array key ภายใน controller ให้เขียนดังนี้ echo $this->lang->get('index_welcome');การแปลภาษาจาก view ที่เป็น .php, .phtml ให้เขียนดังนี้ <?php echo $t->_('index_hello_X', array('name' => 'User')); ?>
การแปลภาษาจาก view ที่เป็น .volt ให้เขียนดังนี้ {{t._('index_welcome')}}

เพียงเท่านี้เราก็สามารถรองรับไฟล์ภาษาที่หลากหลายและสามารถเรียกโหลดได้ครั้งละหลายๆไฟล์ได้แล้วครับ.

สำหรับไฟล์ต้นแบบและรายละเอียดต่างๆเพิ่มเติมโปรดดาวน์โหลดได้จาก Github
Download

 

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>