PHP How to handle different languages in config file? - php

I am working on a project and there are going to be 3 different languages: English, French and Spanish. This will be defined when the user signs up.
Now in my config file I have the following:
define("DEFAULT_SLOGAN", "The default slogan will go here.");
Until I started realizing that I needed to accept different languages.
The user has an assigned language code (EN, FR, SP). How would I go about having different language strings for each page? Would I need to have something like this:
define("DEFAULT_SLOGAN_EN", "Slogan in english");
define("DEFAULT_SLOGAN_FR", "Slogan in french");
define("DEFAULT_SLOGAN_SP", "Slogan in spanish");
And for each string just have 3 different versions of it? Not too sure the best way to approach this.
Thanks!

A simple strategy that is often used are arrays:
$lang['en'] = array(
'DEFAULT_SLOGAN' => 'The default slogan will go here.',
);
// Same for other languages
Then, in your actual code, make sure that $lang is available (you can use the global keyword for this) and use these arrays.

A better approach would be to create
locale_definitions_EN.php containing
define("DEFAULT_SLOGAN", "The english default slogan will go here.");
local_definitions_FR.php containing
define("DEFAULT_SLOGAN", "The french default slogan will go here.");
etc. and then do something like
include "locale_definitions_$userLocale.php";
This has the advantage, that you don't need the memory to hold the unused other language constants.

Related

Display word endings depend on gender (Polish language issue), PHP

This problem might not occure in English, but does really hurt in Polish language. I guess that my question is mostly for Polish users since they might already have a decent solution.
What I mean, is that the verbs in Polish language, are different for male and female in past time. And there are dozens of different options. If my script need to display lots and lots of text - it really becomes a painful problem to deal with. Short example (not very elegant use of language, but for demonstration purpose):
Male: On poszedł i nie znalazł, więc klasnął w dłonie i nagle go coś pożarło.
Female: Ona poszła i nie znalazła, więc klasnęła w dłonie i nagle ją coś pożarło.
I managed to find such an solution: each time at the beginning of script, I prepare variable that looks like that:
$verb[$ending][$sex] = 'something';
//$ending does contain - for my convenience - letters that says what kind of eding am I changing, instead of numeric options
//Examples:
$verb['-a']['male'] = '';
$verb['-a']['female'] = 'a';
//works for On=>Ona, znalazł=>znalazła
$verb['al-ela']['male'] = 'ął';
$verb['al-ela']['female'] = 'ęła';
//works for klasnął=>klasnęła
Now if I add fact, that 99% of time I don't know from the beginning what kind of sex am I dealing with, my variable start look kinda scary: $verb['al-ela'][$_SESSION['user'.$id]['sex']]. So my end text does look like that:
O'.$verb['-a'][$_SESSION['user'.$id]['sex']].' posz'.$verb['edl-la'][$_SESSION['user'.$id]['sex']].' i nie znalazł'.$verb['-a'][$_SESSION['user'.$id]['sex']].', więc klasn'.$verb['al-ela'][$_SESSION['user'.$id]['sex']].' w dłonie i nagle '.$verb['go-ja'][$_SESSION['user'.$id]['sex']].' coś pożarło.
Yes, sure - this is rather extreme example, but sometimes text really does look like that and it is unavoidable.
To make long story short, here are my questions:
Am I doing it wrong? Is there a better/faster/more handy solution for such type of problems?
Is there a script that might detect/change endings for me without ruining rest of the text?
I struggled to find full list of possible ending variations in Polish (for both singular, and plural), so I'm creating my own list as I'm finding new options. Perhaps someone does have a list like that => it might help me to create script from my 2nd question.
Thanks a lot in advance, best regards!

Storing string templates

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.
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.
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?
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.
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

CakePHP Localization issue

I follow the cakePHP book to config the i18n and l10n of my site. Suppose I have English, Spanish, German.
Browsers usually have a list of languages ordered by priority. If at the first position of the list appears a language that is configured on my site all works fine. But if, for example, the list is this: French, German, Spanish, English; localization fails and shows the i18n identifiers that I used in my code.
I would like that in this case the page appears translated in German, because in the list is the first language that my site can provide. This would be the perfect solution but at least I would like that I can configure a default language (for example, English) if the first language of the browser list is unknown by my page.
What I can do to achieve this and where should put the code?
Thank you in advance.
Well, I did the next in app/Config/bootstrap.php:
$browserLangs = explode(',',$_SERVER['HTTP_ACCEPT_LANGUAGE']);
$find = 'eng';
foreach($browserLangs as $browserLang) {
$lang = substr($browserLang,0,2);
var_dump($lang);
if ($lang == 'en') {
$find = 'eng';
break;
} else if ($lang == 'es') {
$find = 'spa';
break;
} else if ($lang == 'ca') {
$find = 'cat';
break;
}
}
Configure::write('Config.language', $find);
In Accept Language header we have a string with all the list of languages of the browser. Then I define English as default, and try to find other language in the list, breaking the bucle when I get one.
I put this as an answer because it works, but its dirty because:
I don't know if this is the best way and the best place,
this is very static, you have to put the languages one by one and when you add another language you have to update the algorithm. The best solution will check automatically for the locales defined on the cake application, which I don't know how to ask for.
Perhaps this code should be defined as a plugin, component or something like that.
Perhaps someone can improve this or post another better solution as a new answer.
You will have to open the created .po file, with an editor (for example: http://poedit.net) there you can create multiple language versions.
Dont forget to change the plural forms for every language, for german it's nplurals=2; plural=(n != 1), here a list: http://translate.sourceforge.net/wiki/l10n/pluralforms.
After that, you'll have to save the language file in app/Locale/deu/LC_MESSAGES/...
You should have a default.mo and default.po file in that folder at the end.

Gettext genitive

Let's say I want to display the following text to the user:
"John's car"
I'm using gettext to output it:
sprintf(_("%s's car"), $firstName)
The obvious problem is that, if the first name of the user ends in an S, we'll get something like this:
"James's car" (should be "James' car")
How do I solve that problem, especially considering that other languages might pose a similar challenge (different genitives depending on word ending or similar)?
Simply put: write a function with an explicit if to get the genitive form for a name, and print that instead. I'd provide an example but you haven't indicated what language we're talking about!

How does gettext handle dynamic content?

In php (or maybe gettext in general), what does gettext do when it sees a variable to dynamic content?
I have 2 cases in mind.
1) Let's say I have <?=$user1?> poked John <?=$user2?>. Maybe in some language the order of the words is different. How does gettext handle that? (no, I'm not building facebook, that was just an example)
2) Let's say I store some categories in a database. They rarely, but they are store in a database. What would happen if I do <?php echo gettext($data['name']); ?> ? I would like the translators to translate those category names too, but does it have to be done in the database itself?
Thanks
Your best option is to use sprintf() function. Then you would use printf notation to handle dynamic content in your strings. Here is a function I found on here a while ago to handle this easily for you:
function translate()
{
$args = func_get_args();
$num = func_num_args();
$args[0] = gettext($args[0]);
if($num <= 1)
return $args[0];
return call_user_func_array('sprintf', $args);
}
Now for example 1, you would want to change the string to:
%s poked %s
Which you would input into the translate() function like this:
<?php echo translate('%s poked %s', $user1, $user2); ?>
You would parse out all translate() functions with poEdit. and then translate the string "%s poked %s" into whatever language you wanted, without modifying the %s string placeholders. Those would get replace upon output by the translate() function with user1 and user2 respectively. You can read more on sprintf() in the PHP Manual for more advanced usages.
For issue #2. You would need to create a static file which poEdit could parse containing the category names. For example misctranslations.php:
<?php
_('Cars');
_('Trains');
_('Airplanes');
Then have poEdit parse misctranslations.php. You would then be able to output the category name translation using <?php echo gettext($data['name']); ?>
To build a little on what Mark said... the only problem with the above solution is that the static list must be always maintained by hand and if you add a new string before all the others or you completely change an existing one, the soft you use for translating might confuse the new strings and you could lose some translations.
I'm actually writing an article about this (too little time to finish it anytime soon!) but my proposed answer goes something like this:
Gettext allows you to store the line number that the string appears in the code inside the .po file. If you change the string entirely, the .po editor will know that the string is not new but it is an old one (thanks to the line number).
My solution to this is to write a script that reads the database and creates a static file with all the gettext strings. The big difference to Mark's solution is to have the primary key (let's call it ID) on the database match the line number in the new file. In that case, if you completely change one original translation, the lines are still the same and your translator soft will recognize the strings.
Of course there might be newer and more intelligent .po editors out there but at least if yours is giving you trouble with newer strings then this will solve them.
My 2 cents.
If you have somewhere in your code:
<?=sprintf(_('%s poked %s'), $user1, $user2)?>
and one of your languages needs to swap the arguments it is very simple. Simply translate your code like this:
msgid "%s poked %s"
msgstr "%2$s translation_of_poked %1$s"

Categories