-3

Template Strings.

This link might help a little bit: Does PHP have a feature like Python's template strings?

What my main issue is, is to know if there's a better way to store Text Strings.

Now, is this normally done with one folder (DIR), and plenty of single standalone files with different strings, and depending on what one might need, grab the contents of one file, process and replace the {tags} with values.

Or, is it better to define all of them inside one single file array[]?

greetings.tpl.txt

['welcome'] = 'Welcome {firstname} {lastname}'.
['good_morning'] = 'Good morning {firstname}'.
['good_afternoon'] = 'Good afternoon {firstname}'.

Here's another example, https://github.com/oren/string-template-example/blob/master/template.txt

Thx in advance!

Answers that include solutions, that state that one should use include("../file.php"); are NEVER ACCEPTED HERE. A solution that shows how to read a LIST of defined strings into an array. The definition is already array based.

Community
  • 1
  • 1
John Smith
  • 465
  • 4
  • 15
  • 38
  • 1
    thinking aloud, As usual, it depends? Whatever, you can use the same data storage techniques in `PHP` that you do in 'Python'. So, just duplicate what you do in Python as regards storage. Time it. I suspect it will be simila?. And familiar too you? The alternative is something compiled like `twig` with `caching`. Which may be worthwhile doing 'long term?' – Ryan Vincent Jan 03 '16 at 13:57
  • I think, things in PHP are a little bit different from Python. My concern still is, storing these for reediness to process when need e.g. when loading an object. – John Smith Jan 03 '16 at 13:59
  • 2
    Until you time it then you will not know? Seriously, do what you are used to and try it. If the customer is happy then you are done. You can always switch to a 'cached' approach - if required, later. Without timings then it is just speculation. i.e You don't know for any one web transaction how much of the `templates` are actually used. Could be a small part of normally. Why load stuff you don't need. Adding a `cache` like `redis` is not difficult and will speed it up significantly. But do that later? – Ryan Vincent Jan 03 '16 at 14:12

4 Answers4

6

To add values to templates, you can use strtr. Example below:

$msg = strtr('Welcome {firstname} {lastname}', array(
    '{firstname}' => $user->getFistName(),
    '{lastname}' => $user->getLastName()
));

Regarding storing strings, you can save one array per language and then load only relevent one. E.g. you'll have a directory with 2 files:

  • language
    • en.php
    • de.php

Each file should contain the following:

<?php

return (object) array(
    'WELCOME' => 'Welcome {firstname} {lastname}'
);

When you need translations, you can just do the following:

$dictionary = include('language/en.php');

And the dictionary will then have an object that you can address. Changing the example above, it will be something like this:

$dic = include('language/en.php');
$msg = strtr($dic->WELCOME, array(
    '{firstname}' => $user->getFistName(),
    '{lastname}' => $user->getLastName()
));

To avoid the situation when you don't have the template in dictionary, you can use a ternary operator with the default text:

$dic = include('language/en.php');
$tpl = $dic->WELCOME ?: 'Welcome {firstname} {lastname}';
$msg = strtr($tpl, array(
    '{firstname}' => $user->getFistName(),
    '{lastname}' => $user->getLastName()
));

What people usually do to be able to edit the texts in db, you can have a simple export (e.g. var_export) script to sync from db to files.

Hope this helps.

t1gor
  • 1,244
  • 12
  • 25
  • Thanks a lot for your response here. I still question this, is it really done this way? Do even big sites like e.g. Twitter, Instagram etc. do the same? – John Smith Jan 03 '16 at 14:04
  • Is this approach smart and efficient? – John Smith Jan 03 '16 at 14:08
  • @JohnSmith we use it on a pretty large project. The idea is that files are fast enough for us right now. If this is not the case for you, or you have a largely distributed system, you can save your dictionaries to somekind of key-value storage as Redis or Memcache. – t1gor Jan 03 '16 at 14:14
  • I plan to use Memcache at some stage, for now not. Please share some details about your project, you wrote: "we use it on a pretty large project", and I am interested in hearing more. What project? How many users use it today? – John Smith Jan 03 '16 at 14:37
  • 1
    About 40K page views daily (dictionaries are loaded per each page view) on a half a million user base. The top most load was about 150K page views and there were no issues with dictionary, even despite we are using NFS on 3 servers. – t1gor Jan 03 '16 at 14:41
  • So you actually have plenty of files in some directory that act as String Templates? Hmm interesting. Do you generate activity feeds for users? – John Smith Jan 03 '16 at 14:45
  • 1
    We have 5 languages, so 5 files. It acts as a part of localization system we have. No, we don't have feeds, but how is this relevant? – t1gor Jan 03 '16 at 14:46
  • Is your websites online now to try? – John Smith Jan 03 '16 at 14:47
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/99596/discussion-between-t1gor-and-john-smith). – t1gor Jan 03 '16 at 14:48
  • 1
    This is an efficient solution. It only loads the relevant language. Usually there is an efficient file chasing on the OS, that takes case of which are most used and keeps them in memory. This solution plays to this behavior. – Simon Rigét Jan 09 '16 at 09:50
  • 1
    Personally i prefer to use the printf format, instead of inventing a string substitution. – Simon Rigét Jan 09 '16 at 09:52
  • @SimonRigét, explain about the "printf format, instead of inventing a string substitution", thanks a lot. – John Smith Jan 12 '16 at 18:24
1

First, take a look at gettext. It is widely used and there is plenty of tools to handle translation process, like xgettext and POEdit. It is more comfortable to use real english strings in source code and then extract them using xgettext tool. Gettext can handle plural forms of practically all languages, which is not possible when using simple arrays of strings.

Very useful function to combine with gettext is sprintf() (or printf(), if you want to output text directly).

Example:

printf(gettext('Welcome %s %s.'), $firstname, $lastname);
printf(ngettext('You have %d new message.', 'You have %d new messages.',
    $number_of_new_messages), $number_of_new_messages);

Then, when you want to translate this into language where last name usually precedes first name, you can use this: 'Welcome %2$s, %1$s.'

The second example, the plural form, can be translated using more than two strings, because part of localization file is how plural forms are arranges. While for english it is nplurals=2; plural=(n != 1);, for example in czech it is nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; (three forms, first is for one item, second for 2 to 4 items and third for the rest). For example Irish language has five plural forms.

To extract strings from source code use xgettext -L php .... I recommend writing short script with the exact command fitting your project, something like:

# assuming this file is in locales directory
# and source code in src directory
find ../src -type f -iname '*.php' > "files.list"
xgettext -L php --from-code 'UTF-8' -f "files.list" -o messages.pot

You may want to add custom function names using -k argument.

Josef Kufner
  • 2,851
  • 22
  • 28
1

OK John I will elaborate.

The best way is to create a php file, for each language, containing the definition of an array of texts, using printf format for string substitution. If the amount of text is very large, you might consider partitioning it further. (a few MB is usually fine)

This is efficient in production, assuming the OS has a well tuned file cash. Slightly more so, it you use numerical indexes to the array.

It is much more efficient to let php populate the array, then to do it your self, reading a text file. this is after all, I assume, static text? If production performance is not an issue, please disregard this post.

greetings_tpl_en.php

$text_tpl={
   'welcome' => 'Welcome %s %s'
  ,'good_morning' => 'Good morning %s'
  ,'good_afternoon' => 'Good afternoon %s'
};

your.php

$language="en";
require('greetings_tpl_'. $language .'php');
....
printf($text_tpl['welcome'],$first_name,$last_name);

printf i a nice legacy from the C language. sprintf returns a string instead of outputting it.

You can find the full description of the php printf format here: http://php.net/manual/en/function.sprintf.php

(Do read Josef Kufner post again, when this is solved. +1 :c)

Hope this helps?

Simon Rigét
  • 2,757
  • 5
  • 30
  • 33
  • What about opening a file instead of require('...'); ? – John Smith Jan 14 '16 at 10:32
  • There are only performance issues with that. Is that important to you? When using require/include and populating an array from php code, you utilize the compiled php engine to do the job. If you process the texts in php, it is done in an interpreter. Its a heavier job and harder for the system to cash. So its depends on your need for speed vs. how you like to code :c) What is your concern with include? – Simon Rigét Jan 14 '16 at 12:07
  • It seems very unprofessional, I just get that feeling, maybe I am completely wrong. – John Smith Jan 14 '16 at 12:16
  • 1
    Yes :c) To me professionalism is to choose the best solution to a problem. If it was a C program I would totally agree. But as with most web programming, you have to make do with a rusty bucket, and make it run like hell :c) But do add comments in you code to explain your self! I do - and I often need to read them my self later :c) – Simon Rigét Jan 14 '16 at 12:27
0

You could store all the templates in one associative array and also the variables that are to replace the placeholders, like

$capt=array('welcome' => 'Welcome {firstname} {lastname}',
            'good_morning' => 'Good morning {firstname}',
            'good_afternoon' => 'Good afternoon {firstname}');

$vars=array('firstname'=>'Harry','lastname'=>'Potter', 'profession'=>'wizzard');

Then, you could transform the sentences through a simple preg_replace_callback call like

function repl($a){ global $vars;  
  return $vars[$a[1]];
}
function getcapt($type){ global $capt; 
  $str=$capt[$type];
  $str=preg_replace_callback('/\{([^}]+)\}/','repl' ,$str);
  echo "$str<br>";                      
}
getcapt('welcome');
getcapt('good_afternoon');

This example would produce

Welcome Harry Potter
Good afternoon Harry
Carsten Massmann
  • 26,510
  • 2
  • 22
  • 43
  • I already have code that processes and replaces the tags with values, so my initial concern isn't in the code itself, my issue, remains around String storage. For example, there are activity feeds, which usually, include predefined String Texts, and tags are replaced with values, here's an example: https://raw.githubusercontent.com/caseyscarborough/github-activity/gh-pages/images/matz.png – John Smith Jan 03 '16 at 14:43