6

I have been building a personal framework of mine which started off as a way to learn the MVC pattern and has progressed now into something that I like more than most frameworks out there (which is probably because I add what I like and change what I don't like but nevertheless) and for good or bad I use it in some projects.

The problem I have now is that I can't come up with a decent way to access my i18n functionality (it's not really i18n it's just translations, doesn't include full i18n support, at least not yet).

The way it works is I use configuration files as language files because I figured it would be pretty convenient to use the Config class to load them since in my framework config files are loaded dynamically - not loaded unless needed, you can grab a glance here

class Config {

    private static $settings = array();

    private function __construct() {

    }

    public static function load($file) {
        $path = PROJECT_PATH . '/config/' . $file . '.php';
        if (is_file($path)) {
            $settings = require($path);
        } else {
            throw new Exception('Configuration file [' . $file . '] doesn\'t exist', 500);
        }

        self::$settings[$file] = $settings;
        return true;
    }

    public static function get($file = null) {
        if ($file === null) {
            return self::$settings;
        } elseif (isset(self::$settings[$file]) || self::load($file)) {
            return self::$settings[$file];
        }
    }
}

Where a single configuration file would look something like this

<?php return array(
    'setting0' => 'value',
    'setting1' => 'value',
    ....
);

That allows PHP to cache those files and loading them becomes very fast.

Now on to translations, as I said they are configuration files in a different directory named lang, but I can't just go around calling Config::get('lang/en/myLangFile') every time I need to access a translation, so I invented (invented, huh) the Translations class, that represents a single translations file

class Translations {

    protected $data = [];

    public function __construct(array $translations) {
        $this->data = $translations;
    }

    public function __get($name) {
        return isset($this->data[$name]) ? $this->data[$name] : $name;
    }

}

Now it is super convenient and beautiful to access translations

$t = new Translations([...]);

echo $t->translationKey;

I have a Lang class that is used to set up the user's preferred language among other little things so I figured I would use that as a factory for my Translations classes

class Lang {

    public static function get($file) {
        return new Translations(Config::get('lang/' . self::$lang . '/' . $file));
    }

}

So now all I have to do in order to grab some translations is

$t = Lang::get('myLangFile');

echo $t->translationKey;

In case you are wondering why I have so much static stuff it is because these classes make no sense to be instantiated and I don't like the singleton design pattern, I prefer to have "static classes" even though they are not supported in php (yet?).

So far so good, I have got the translations going, but lets get to the problem (finally).

When a view is rendered it is most likely going to print some text out to the user and for that I need to have a translations object available inside but it is pretty inconvenient to have to pass that from the controller because I would have to go and put that on each method and that would be hell, furthermore if I do that then some other controller calls the same view without the correct translations objects stuff is going to break, which makes sense but adds complexity to the program.

What I have been doing up until this point is on the top of every view I construct my translations object

<?php $t = Lang::get('myLangFile') ?>

<div><?= $t->helloWorld ?></div>

This works and guarantees me my views will work regardless of who's calling them and basically costs nearly nothing in terms of performance because instantiating a Translations will not copy the array containing the information unless a change is introduced since the code in the constructor is just an assignment, so I guess there's no problem with that but it's just bugging me for some reason that it is not the correct thing to do.

Furthermore I will need to use a Translations class in a model or validator occasionally and I would need to instantiate it there too, so in a single execution I may be instantiating the same Translations object multiple times. To solve this problem I would need to start putting those objects into the registry and I think this would be getting too far.

I would like to see what are some thoughts on this approach as I could be blinded by my own such and possibly get some useful advice and stuff. Thanks in advance to anyone who chose to spare their time with my problem!

0

<?php $t = Lang::get('myLangFile') ?>

<div><?= $t->helloWorld ?></div>

This works and guarantees me my views will work regardless of who's calling them and basically costs nearly nothing in terms of performance because instantiating a Translations will not copy the array containing the information unless a change is introduced since the code in the constructor is just an assignment, so I guess there's no problem with that but it's just bugging me for some reason that it is not the correct thing to do.

I see what you mean...
I think that one thing that could be bugging you is that by adding into the view the responsibility of loading the language files, you are breaking the Inversion of Control design pattern
Because of that, if for example, your 'myLangFile' gets too big, and you want to split it into 2 separate files, you can't do it without changing the view code.
what do you think?

  • Exactly, that is absolutely a valid reason. – php_nub_qq Jun 11 '16 at 11:13
  • 1
    Really? The language file can't include other files in itself? Or read from a database? – svidgen Jan 7 '17 at 13:47
  • @svidgen that is extremely bad design – php_nub_qq Feb 21 '17 at 9:08

Your Answer

By clicking "Post Your Answer", you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.