Replace a text by it's equivalent in PHP

124 views Asked by At

EDIT: I know that gettext, i18n and other stuff already exists, but this is for learning purposes.

I'm working on a way to translate my websites by using .json files that contains the wanted translations in different languages.

So I made an index.php that calls my class function Parser::render($file), here's the unfinished code:

home.php:

<h1>{website.welcome}</h1>
<h2>{website.tagline}</h2>
<p>{website.home.introduction}</p>

lang-fr.json:

{
    "_lang":
    {
        "author": "ThanosS",
        "version": "0.0.3",
        "flag": "fr",
        "country": "France",
        "state": "100%"
    },

    "website":
    {
        "title": "Mon putain de site",
        "welcome": "Bienvenue",
        "tagline": "Je suis un texte en français.",

        "home":
        {
            "introduction": "Ceci est un exemple pour gérer les traductions sur un site web"
        }
    }
}

lang-en.json:

{
    "_lang":
    {
        "author": "ThanosS",
        "version": "0.0.2",
        "flag": "en",
        "country": "USA",
        "state": "100%"
    },

    "website":
    {
        "title": "My fucking website",
        "welcome": "Welcome",
        "tagline": "I'm a text in english.",

        "home":
        {
            "introduction": "This is an example to handle website translations properly"
        }
    }
}

Parser.class.php:

<?php
/**
*   Translation Example
*   @version   0.0.1
**/

class Parser
{
    private $langsPath = '';
    private $langsExt = 'json';
    private $currentLang = 'en';

    public function __construct($langsPath, $langsExt = 'json', $currentLang = 'en')
    {
        $this->langsPath = $langsPath;
        $this->langsExt = ($this->langsExt != $langsExt) ? $langsExt : $this->langsExt;
        $this->currentLang = ($this->currentLang != $currentLang) ? $currentLang : $this->currentLang;
    }

    public function render($file)
    {
        $cLang = json_decode($this->loadLangFile($this->currentLang));
        $cFile = $this->loadPageFile($file);

        $langInfos = $cLang->_lang;
        unset($cLang->_lang);

        if (preg_match_all('/{([^}].*)}/i', $cFile, $matches))
        {
            $keys = $matches[1];
            foreach ($keys as $value)
            {
                $indexs = explode('.', $value);



            }
        }

        return $parsedFile;
    }

    private function loadLangFile($lang)
    {
        return file_get_contents($this->langsPath.'lang-'.$lang.'.'.$this->langsExt);
    }

    private function loadPageFile($page)
    {
        return file_get_contents($page);
    }
}

I want to find a way to convert {key.other.key} to it's value in the current lang file (something like <?php echo $cLang->key->other->key; ?>)

So Parser::render('home.php'); should return :

<h1>Bienvenue</h1>
<h2>Je suis un texte en français</h2>
<p>Ceci est un exemple pour gérer les traductions sur un site web</p>

Thanks in advance, i searched for 2 days ago but my brain isn't evolved to do that :/

1

There are 1 answers

4
arkascha On BEST ANSWER

OK, so your question is how to navigate inside that decoded json structure...

Here is a working example. No error handling is implemented to keep things simple. It should help you on the track :-) I left the language files and the home.php template unchanged. The following index.php file is just for this demo, to use your parser class below. I slightly decluttered the implementation of that parser class whilst on it. But basically the only change is that I replaced your preg_match() call with a direct call to preg_replace_callback() and coded the required dictionary navigation as anonymous function which is used as callback:

index.php:

<?php
require 'parser.class.php';
$parser = new Parser('./', 'json', 'fr');
$output = $parser->render('home.php');
echo $output;

parser_class.php:

<?php
class Parser
{
    const LANG_PATH = '.';
    const LANG_EXT  = 'json';

    private $langsPath = '';
    private $langsExt = 'json';
    private $langCode = 'en';
    private $langInfos = [];

    protected $dictionary;

    public function __construct($langCode = 'en')
    {
        $this->langCode = $this->langCode;
        $this->dictionary = $this->loadDictionary();
        $this->langInfos  = $this->dictionary;
        unset($this->dictionary->_lang);
    }

    public function render($file)
    {
        $rawPayload = file_get_contents($file);

        $transPayload = preg_replace_callback(
            '/({([^}]+)})/',
            function($match) {
                $matches = explode('.', $match[2]);
                $_p = &$this->dictionary;
                foreach ($matches as $step) {
                    if (property_exists($_p, $step)) {
                        $_p = &$_p->$step;
                    } else {
                        return '-- undefined i18n string --';
                    }
                }
                return $_p;
            },
            $rawPayload);

        return $transPayload;
    }

    private function loadDictionary()
    {
        // here a lot of error handling is required, probably throwing exceptions
        $langFilePath = sprintf('%s/lang-%s.%s', self::LANG_PATH, $this->langCode, self::LANG_EXT);
        $dictionaryContent = file_get_contents($langFilePath);
        return json_decode($dictionaryContent);
    }
}

You were pretty much there yourself, that is why I insisted in getting you to specify what your actual issue with coding this is. But I know these situations myself: nearly there, all is clear, except... :-)

Anyways: this basically boils down to using an internal "pointer" inside the decoded dictionary. You extract the translatable strings by means of a regex in a greedy manner. Those strings are exploded into their parts of a "dot notation" (your approach). Those parts ("steps") are then used to navigate inside the dictionary by moving the pointer always one step further towards the final translated string. That's all.

Have fun!