0

Until now, unless I made a multilingual website (where I would use .mo & .po files), all the text would be scrambled all around the template and / or class files. Instead, I would like to store all static text in a file that is easily editable by my coworkers and clients (that rules out database storage and POedit).

I made a JSON file that stores the messages / static text like this:

{
  "titles": {
    "main_title": "This is the main title of the website",
    "login_page_title": "Please, sing in",
    "about_page_title": "About us"
  },

  "errors": {
    "empty_required_field": "This field is required.",
    "database_connection_error": "Couldn't connect to the database.",
  }

}

Then I import it in the index.php file:

$messages = json_decode(file_get_contents("messages.json"));

And use it like:

echo($messages->titles->main_title);

Which has been working so far so good (although I'm uncertain that there aren't better ways to archieve this). At least in the template pages where everything is html with minimal logic.

But I'm having trouble using the strings from the JSON file inside the classes' functions. I would like to use the error messages when throwing exceptions, for example. But I'm quite reluctant about stating "global $message" in every function where it's used (feels repetitive). Also everybody says that globals are naughty.

So my questions are two:

1) Is the JSON file a good way to handle my problem? (and if not, why, and which method would be better?).

2) How could I retrieve the stored strings from inside the classes? I'm thinking something like extending the Exception class to include the error messages, but I'm unsure of how to do it.

Thanks in advance for your help.

Sandra
  • 115
  • 1
  • 1
  • 6
  • 1
    You could add a Language object either as a Singleton or as an injected dependency in your other classes, and use a syntax such as `Language::_('titles/main_title')` -- you could also move there all sorts of internationalization code. – LSerni Aug 03 '15 at 19:34
  • 1
    Not sure I like this approach. If you have a website of any size, you are going to end up reading from disk and then serializing the entire website's worth of content for every page request. I would suggest using some sort of data structure where you can look up only content needed for rendering an individual page from an in-memory store. – Mike Brant Aug 03 '15 at 19:39
  • The project in question is a very small website that can be handled in a single file, but it never hurts to look beyond. – Sandra Aug 03 '15 at 19:48
  • 1
    Instead of doing `global $messages`, put `Array $messages` as a parameter of global functions or in the constructor for objects. Also, if you use bionicrm's idea, it saves you from having to encode/decode JSON. – Mike Aug 03 '15 at 20:13
  • Thanks for all the answers people. I might be unable to answer anymore until tomorrow, so I may delay choosing an accepted answer. I'm thinking something among the lines of making an Interface with all the translatable concepts, an EnglishLanguage (or whatever) class that implements it... But I've never worked with interfaces (n00b here) so I might be talking nonsense. – Sandra Aug 03 '15 at 20:56

2 Answers2

1

One approach, which Laravel takes, is creating some sort of directory tree like the following:

lang/
  en/
    titles.php
    errors.php

titles.php could contain the following:

<?php

return [
  'main_title' => 'This is the main title of the website',
  'login_page_title' => 'Please, sing in',
  'about_page_title' => 'About us'
];

As for errors.php:

<?php

return [
  'empty_required_field' => 'This field is required.',
  'database_connection_error' => "Couldn't connect to the database.",
];

I don't really like the JSON approach because it's not very flexible. For one, in PHP files, you have access to any variables you may want to give it, there's comments, possibility of using functions to create some messages, etc. This is why I recommend the above method.

In order to get the messages, you would require the file in a variable, like $titles = require 'lang/en/titles.php', using it like: $titles['main_title']. This method also makes it easy to change the language if needed.

While I'm not 100% sure I understand your exception problem, you would throw an exception with the appropriate message like: throw new Exception($errors['empty_required_field']);

TJ Mazeika
  • 942
  • 1
  • 9
  • 30
  • Thanks for your answer. About the Exceptions and such, my problem was using the strings inside functions that throw messages without including the file inside every function; or declaring global the variable that stores the strings. – Sandra Aug 03 '15 at 20:38
  • 1
    Ah, I see. So while you don't have to use a global variable, you may want to try having a global function, such as `lang($path)`, where `$path` could be a dot notated path to the field you want, like `'titles.main_title'`, and the function would look that up and return the correct value (in the correct language as well). – TJ Mazeika Aug 03 '15 at 20:42
  • 1
    If you are using PHP >= 5.6 you can also use [constants](http://stackoverflow.com/questions/1290318/php-constants-containing-arrays) to store an array of texts. – Mike Aug 03 '15 at 21:59
0

In the end I opted for a Singleton class that loads/includes a separate text file. Nice global scope and should be easy to adapt to other needs (multilingüal, separate language files, or whatever). As I said I'm no expert so all critique is welcome.

<?php

class CustomText {

private static $instance = null;
private static $text;

private function __clone() {}

// On construct, checks if the strings are stored in a session.
// If not, retrieves them from file and stores them in a session.
private function __construct() {
    if(self::isStoredInSession() == true) {
        self::$text = $_SESSION["custom_text"];
    } else {
        //self::$text = json_decode(file_get_contents("messages.json"),true);
        self::$text = include_once("messages.php");
        self::saveToSession();
    }
}

// Private initialization called on every public method so I don't have to worry about it on other files.
private static function initialize() {
    if(self::$instance == null) {
        self::$instance = new self;
    }
}

// Session management
private static function saveToSession() {

    if(session_status() == PHP_SESSION_NONE) {
        session_start();
    }

    if(!isset($_SESSION["custom_text"])) {
        $_SESSION["custom_text"] = self::$text;
    }
}

private static function isStoredInSession() {

    if(session_status() == PHP_SESSION_NONE) {
        session_start();
    }

    if(isset($_SESSION["custom_text"])) {
        return true;
    }

    return false;
}

// Sample public functions
public static function getText($section,$string){

    self::initialize();

    if(isset(self::$text[$section][$string])) {
        return self::$text[$section][$string];
    } else {
        return "";
    }

}

public static function getError($string) {

    self::initialize();

    if(isset(self::$text["error"][$string])) {
        return self::$text["error"][$string];
    } else {
        return "";
    }

}

public static function getWebsiteTitle($section,$divider = " - ") {

    self::initialize();

    $title = "";

    if(isset(self::$text["title"]["main"])) {
        $title .= self::$text["title"]["main"];
    }

    if(isset(self::$text["title"][$section])) {
        if(!empty($title)) {
            $title .= $divider;
        }

        $title .= self::$text["title"][$section];
    }

    return $title;
    }

}

What worries me the most is that I'm not sure that storing the data in a session is better that including a file on each page, and I have everything twice in the session variable and the class parameter.

Sandra
  • 115
  • 1
  • 1
  • 6