views:

215

answers:

6

This is the most optimal way of dealing with a multilingual website I can think of, right now (not sure) which doesn't involve gettext, zend_translate or any php plugin or framework.

I think its pretty straight forward: I have 3 languages and I write their "content" in different files (in form of arrays), and later, I call that content to my index.php like you can appreciate in the following picture:

alt text

I just started with php and I would like to know if I'm breaking php good practices, if the code is vulnerable to XSS attack or if I'm writing more code than necessary.

EDIT: I posted a picture so that you can see the files tree (I'm not being lazy)

EDIT2: I'm using Vim with the theme ir_black and NERDTree.

+7  A: 

Looks all right to me, although I personally prefer creating and using a dictionary helper function:

<?php echo dictionary("showcase_li2"); ?>

that would enable you to easily switch methods later, and gives you generally more control over your dictionary. Also with an array, you will have the problem of scope - you will have to import it into every function using global $language; very annoying.

You will probably also reach the point when you have to insert values into an internationalized string:

You have %1 votes left in the next %2 hours.
Sie haben %1 stimmen übrig für die nächsten %2 stunden.
Sinulla on %1 ääntä jäljellä seuraavan %2 tunnin ajassa.

that is something a helper function can be very useful for:

<?php echo dictionary("xyz", $value1, $value2 ); ?> 

$value1 and $value2 would be inserted into %1 and %2 in the dictionary string.

Such a helper function can easily be built with an unlimited number of parameters using func_get_args().

Pekka
lol great answer I like it.
Andy E
@Andy I had to expand it a bit. :)
Pekka
Thanks, I'll try that, but how does it looks like in the lang.xx.php file (do I have to change all my arrays in my lang.xx.php files to dictionary()?
janoChen
No, you can use `global $language;` in the `dictionary()` function. Basic usage: `function dictionary($word) { global $language; return $language[$word]; }` The 100% best thing in terms of performance would be defining the `$language` array directly in the dictionary function but that makes it harder to maintain, I wouldn't do that.
Pekka
@Pekka Why not make a static class dictionary that would perform all the operations without having to use global. And instead of defining the array directly in the dictionary class, use an static function to 'load' (require) the language file. If you want an example for this, send me a message.
St. John Johnson
@St. John I actually thought about using a static class as well, it's a very nice way too. Feel free to post it here, edit it into my answer, or create an answer of your own.
Pekka
@St. John Johnson, yeah an example would be nice thanks (but how do I send a message from here)? @Pekka Is that '$language' variable the same as the '$lang' variable (or array) I have in my code?
janoChen
@jano Yeah, it is the same, my mistake. St. John will get the message because of the @.
Pekka
A: 

The only way that this could be xss is if you have register_globals=On and you don't set $lang['showcase_lil'] or other $lang's. But I don't think you have to worry about this. So I think your in the clear.

as an xss test: http://127.0.0.1/whatever.php?lang[showcase_lil]=alert(/xss/)

Rook
+2  A: 

Looks all right to me also, but:

Seems that you have localization for multiple modules/sites, so why not break it down to multidimensional array?

$localization = array(
  'module' => (object)array(
    'heading' => 'oh, no!',
    'perex'   => 'oh, yes!'
  )
);

I personally like to creat stdClass out of arrays with

$localization = (object)$localization;

so you can use

$localization->module->heading;

:) my 2 cents

Adam Kiss
+1 This is indeed a nice trick. I also use this for my config class :)
AntonioCS
-1 I just noticed that you are missing the > in the = of the array and this code doesn't work as expected. It turns the array $localization into an object but not the module array. That is still an array so you can't do $localization->module->heading;and expect to get anything!
AntonioCS
I overlookd few `>`s and one (object) - repaired
Adam Kiss
A: 

Wouldn't it have been better to post code and briefly explain this issue to us?

Anyway, putting each language in its own file and loading it through some sort of language component seems okay. I'd prefer using some sort of gettext, but this is okay too, I guess.

You should make a function for calling the language keys rather than relying on an array, something like <?php echo lang('yourKey'); ?>

K4emic
A: 

One thing to watch for is interpolation; that's really the only place XSS could sneak in if your server settings are sensible. If you at any point need to do something along the lines of translating "$project->name has $project->member_count members", you'll have to make sure you escape all HTML that goes in there.

But other than that, you should be fine.

Matchu
+2  A: 

It's OK generally. For instance, punBB's localization works this way. It is very fast. Faster than calling a function or an object's method or property. But I see a problem with this approach, since it doesn't support language fallbacks easily. I mean, if you don't have a string for Chinese, let it be displayed in English.

This problem is topical when you upgrade your system and you don't have time to translate everything in every language.

I'd better use something like

lang.en.php

$langs['en'] = array(
    ...
);

lang.cn.php

$langs['cn'] = array(
    ...
);

[prepend].php (some common lib)

define('DEFAULT_LANG', 'en');
include_once('lang.' . DEFAULT_LANG '.php');
include_once('lang.' . $user->lang . '.php');
$lang = array_merge($langs[DEFAULT_LANG], $langs[$user->lang]);
codeholic
Not sure if its the best option but I'm using this to set the default language: switch ($lang) { case 'en': $lang_file = 'lang.en.php'; break; case 'es': $lang_file = 'lang.es.php'; break; case 'tw': $lang_file = 'lang.tw.php'; break; case 'cn': $lang_file = 'lang.cn.php'; break; default: $lang_file = 'lang.en.php'; }
janoChen
IMHO, it is not the best option. I prefer "convention over configuration" approach. http://en.wikipedia.org/wiki/Convention_over_configuration Or use an associative array instead of `switch`, when you're worried if `$user->lang` may contain an invalid value, and check if the value is valid with `if (isset($valid_langs['tlh'])) {`.
codeholic