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 let's 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 and possibly get some useful advice and stuff. Thanks in advance to anyone who chose to spare their time with my problem!