So I just started web development on my new job, meaning I only have very basic knowledge of php, my html, css and js knowledge is a lot better.
I'm trying to build a basic multiple language site via php sessions but I just can't get it to work the way I want.
I have an index.php file and two more folders called EN and DE in which I have the Englisch.html and the Deutsch.html file. The html files only contain some text and it's my goal to have two buttons (or similar) at the top of my site called EN and DE which switch the session to include the files in said folders and display the text beneeth the buttons and keep the language switch function on every page if I had more then one file per language. By default I want the language to be english so load the Englisch.html first.
I'm guessing I need to create an if else statement on every html file that checks the session so something like: if session EN include Englisch.html elseif session DE include Deutsch.html, and on the index.php I somehow need to set the parameters for the session so something like: startSession EN include Englisch.html startSession DE include Deutsch.html ?
I have no idea how far off I am and any help, espacially actual code examples would be greatly appreciated. I hope this described my problem precisely enough.
Your attempted solution is going to bite you in the long run.
It may seem like an easy solution to switch between different files for different languages, yet assume that your website becomes more dynamic, instead of *.html files you want to deal with *.php files, and then would need to have the same logic inside each of your localized files. It doesn't scale well.
I recommend using a translation libary, there are many available, I had good success with symfony's translation, that you can also include in any php project.
Then translation becomes:
$translatedString = $translator->trans("My content");
And the translation can then be maintained in yaml files and depending on the locale, the right language is chosen and each untranslated string will default to English.
And now, whenever your logic changes it is just at one place where you need to adapt it.
I agree with K0pernikus in that your solution won't scale well. You can write a simple translation library yourself in under an hour and you can then test it to see if it will be robust enough for your needs.
The idea is to have language files without any logic in them. You simply call the file you need. All the logic about which language, file, translation key etc. is contained in your library.
I'll keep it very simple and down to a single file; of course, the actual translations will each need to be stored in a file too.
Directory structure:
/locale/en/messages.php
/locale/fr/messages.php
translator.php (this is the main library file and needs to be included on every page)
Within each messages.php file you need to return an array of translation keys and their respective translation. The translation keys are what you will use in your views. These files will become large with many hundreds or thousands of lines for larger applications. If you intend to keep this home-grown solution you'll need to implement caching. That being said, I have files with hundreds of translations and don't notice any significant performance hit.
<?php
// en
return array(
'applicationName' => 'Matt\'s Marvelous Mysteries',
...
<?php
// fr
return array(
'applicationName' => 'Les merveilleux mystères de Matt',
...
Next, you need a library to read these translations and return the actual translation text to you. This file could simply be a collection of helper functions or a full-blown OOP system.
For simplicity, here is a collection of helper methods that get the job done. It does not implement parameterized substitution and you can add that feature relatively easily by making t() accept a 2nd argument, but that is a whole different topic for another time.
The main method in here is t(). It is very simple and accepts a single translation key. Ex. applicationName or greeting.
Firstly, it tries to determine which language to use. It does this in a sequence of priority: URL, session, browser, fallback.
It first tries to get the language/locale from the URL by looking for
a query string parameter named lang. If you think about it, that makes sense because a user intends to switch their language by clicking a
link that says "English" or "French".
If it doesn't find one in the URL it then moves on to check for one
in the session. Again, if it finds it there it uses it.
If it finds it neither in the URL nor the session, then it checks the
browser/headers from the request.
Finally, if it isn't found in any of those 3 locations then it falls
back to the default language as specified in $defaultLanguage.
Once a language has been found, it puts it into the session so the next request doesn't need to go through all that again. It also loads the appropriate messages.php file based on the discovered language.
Finally, once the language has been found and right file has been loaded into memory it searches for the given $key and returns the appropriate translation. If the $key is not found then it simply returns the given $key which will show up in your views so you know something went horribly wrong and you need to start debugging.
<?php
/**
* Performs the actual translation based on the given key. This is the method that is used
* in the actual views to translate a message.
*
* #param $key
* #return mixed
* #throws Exception
*/
function t($key)
{
$language = getLanguage();
$messages = require "{$_SERVER['DOCUMENT_ROOT']}/locale/{$language}/messages.php";
return (array_key_exists($key, $messages))
? $messages[$key]
: $key;
}
/**
* Returns the language as defined by either the URL, session, or browser setting.
* If a language could not be determined, or is not in a list of supported languages, the default
* language passed in to this method will be returned.
*
* #param string $defaultLanguage
* #return string
*/
function getLanguage($defaultLanguage = 'en')
{
$language = null;
if (isset($_GET['lang'])) {
$language = $_GET['lang'];
} elseif (isset($_SESSION['LANG'])) {
$language = $_SESSION['LANG'];
} else {
$language = getLanguageFromBrowser($defaultLanguage);
}
// If the language given to us is not in our list of supported languages, use the default language.
if (!isset($language) || !in_array($language, getSupportedLanguages())) {
$language = $defaultLanguage;
}
// Store the current language to the session for future use.
$_SESSION['LANG'] = $language;
return $language;
}
/**
* Returns the language that the client's browser is set to use. If we're unable to
* determine a language from the browser this will return the default language passed
* in.
*
* #param string $defaultLanguage
* #return int|string
*/
function getLanguageFromBrowser($defaultLanguage = 'en')
{
$languages = [];
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
// break up string into pieces (languages and q factors)
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse);
if (count($lang_parse[1])) {
// create a list like "en" => 0.8
$languages = array_combine($lang_parse[1], $lang_parse[4]);
// set default to 1 for any without q factor
foreach ($languages as $lang => $val) {
if ($val === '') $languages[$lang] = 1;
}
// sort list based on value
arsort($languages, SORT_NUMERIC);
}
}
$supportedLanguages = getSupportedLanguages();
foreach ($languages as $locale => $weighting) {
// We're dealing with locale: Ex. en-US
if (preg_match("/[a-z]{2}-[A-Z]{2}/", $locale)) {
$browserLanguage = substr($locale, 0, 2);
} else {
// Probably dealing with a language: Ex. en
$browserLanguage = $locale;
}
if (in_array($browserLanguage, $supportedLanguages)) {
return $browserLanguage;
}
}
return $defaultLanguage;
}
/**
* Returns an array of languages this web application supports.
*
* #return array
*/
function getSupportedLanguages()
{
return [
'en',
'fr'
];
}
To use it, save these methods into a file called translator.php and then include that file in every page you want to use translations.
Sample:
<?php
session_start();
require_once('translator.php');
// Output your language switcheroo-gadget
if (getLanguage() === 'en') {
echo 'French';
} else {
echo 'English';
}
// Your code... blah blah
// Ahh.. Finally, a translation!
echo '<h1>' . t('applicationName') . '</h1>';
Edit
The last thing I will say is that there is a whole world out there of localization, internationalization (often abbreviated as i18n) which you can learn about.
In my example, I simplistically called it language but often people referer to it as locale but that has a different meaning and syntax. For example, there is a difference between en_CA and en_US and en_GB; all of which are English but have regional differences.
echo t('salutation');
Related
I am starting to add multi-language to a site that uses Laravel for frontend.
However, due to lack of resources at the moment, I am translating the strings as I go. It will be a while before whole site is translated.
The only challenge is that if a text is not translated what get displayed is the key. I would like to specify a default/fallback string for such.
e.g.
{{ trans('site.'.$name) }}
If I pass 'Business' as $name and there' no translation for 'Business' in site.php lang file I end up with site.Business on the frontend. This messes up everything. In the worst case, if there's no site.Business, Laravel should output Business.
Even better it should provide an option for default/fallback string.
Is this possible?
On a side note is there a free translation for common words? This will save time having to translate everything myself.
Thanks.
The fallback language is what you should be using. See the docs
You may also configure a "fallback language", which will be used when
the active language does not contain a given language line. Like the
default language, the fallback language is also configured in the
app/config/app.php configuration file:
'fallback_locale' => 'en',
It will surely take you just as much time writing in a fallback inline as it would simply writing in the fallback in a parallel translation file as you write in the translation key. The time spent thinking of an alternate way versus just doing it is going to be negligible in the end.
If you really want an inline fallback, then you need to create a new helper method that does something different. So prepare yourself for some home brewed awesomeness.
Let's create a new function that we can use in any view. I will use the method described by Joseph Sibler. Create a file called helpers.php inside app. Then add this to your composer.json in the autoload object under a files array as "app/helpers.php". Not sure what I mean? See his answer. After adding, run composer dump-autoload.
Now, let's add a trans_fb() method that will take all the parameters of the trans() method, but with a fallback as well. I will define this method such that the first two arguments are required, (the key, and the fallback).
If Laravel cannot find the translation key (it searches in resources/lang/en/auth.php for example auth.failed as a key) it will use the fallback instead, and pass any other optional arguments for the original method.
<?php
if (! function_exists('trans_fb')) {
/**
* Translate the given message with a fallback string if none exists.
*
* #param string $id
* #param string $fallback
* #param array $parameters
* #param string $domain
* #param string $locale
* #return \Symfony\Component\Translation\TranslatorInterface|string
*/
function trans_fb($id, $fallback, $parameters = [], $domain = 'messages', $locale = null)
{
return ($id === ($translation = trans($id, $parameters, $domain, $locale))) ? $fallback : $translation;
}
}
You can then use this in a template like so:
{{ trans_fb("i.love.laravel", "I love Laravel!") }}
I came up with this solution which uses the same parameters as trans() method and when key is not translated, it returns the translated string using the application 'fallback_locale' from app.php.
I still wonder why it's not implemented natively in Laravel and Laravel just returnes untranslated key.
if (!function_exists('trans_fb')) {
function trans_fb(string $key, array $replace = [], ?string $locale = null)
{
$translation = trans($key, $replace, $locale);
if ($key === $translation) {
return trans($key, $replace, config('app.fallback_locale'));
}
return $translation;
}
}
I need to show my registration page in another language, keeping this separate from his natural behaviour.
In particular this page must be showed in English on the page of the site it owns to, but i need to show it in Italian on another site into an iframe.
Now, common sense tells me i need a page
www.example.com/user
and a
www.example.com/it/user
and i tried to achieve this by "admin/config/regional/language/configure" and the option "Determine the language from the URL (Path prefix or domain)." and all works fine, but, for some reason, as i activate this option, ALL my site pages get a "/en/" in their url, breaking a lot of things into the site (that is extremely complex).
There's a way to achieve this behaviour for the registration page only? (be able to call a page www.example.com/it/user to show registration in italian and having www.example.com/user and all other pages in the site work without any changes?)
My solution would be a small custom module.
Pros:
Easy to tweak and install
Lightweight, code based so you can just check it into VCS and deploy
Can be extended for other language sets
Cons:
Not scaleable.
Requires a dev if you want to change the text
.info file:
name = User Registration Languages
description = Alter the user registration form to present alternative translations
core = 7.x
.module file
<?php
/**
* implements hook_form_FORM_ID_alter()
* https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_form_FORM_ID_alter/7
*
* #param $form
* #param $state
*/
function userreglang_form_user_register_form_alter(&$form, &$state) {
// the arg() function returns a path component (zero based index)
// https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/arg/7
// in this case we're looking for:
// user/register/{language_arg} e.g user/register/it
$lang = arg(2);
// uncomment the following if you want take a look at the form array prior to modification
// be sure to temporarily give the anonymous user role the ability to "Access developer information"
/*if(module_exists('devel')) {
dpm($form);
} // */
if($lang && $map = _userreglang_languages($lang)) {
$form['account']['name']['#title'] = $map['name'];
$form['account']['name']['#description'] = $map['name_desc'];
$form['account']['mail']['#title'] = $map['mail'];
$form['account']['mail']['#description'] = $map['mail_desc'];
$form['actions']['submit']['#value'] = $map['button'];
}
// uncomment the following if you want take a look at the form array after modification
/*if(module_exists('devel')) {
dpm($form);
} // */
}
/**
* helper function for different language maps you might have for the user registration form
*
* #param $lang
* #return array|null
*/
function _userreglang_languages($lang) {
$map = [
'it' => [
'name' => 'Your name, yo!',
'name_desc' => 'Input your username fool!',
'mail' => 'Email, you gots it?',
'mail_desc' => 'Seriously do you not know what email is?',
'button' => 'Click it baby!',
]
];
if(isset($map[$lang])) {
return $map[$lang];
}
else {
return null;
}
}
On drupal.stackoverflow i got a simpler answer. Here it is:
Navigate to /admin/config/regional/language within your site, and verify these things (and correct where appropriate):
Use "English" as your default language
use the "Edit" link for "English" on that page, which will bring you to "/admin/config/regional/language/edit/en". On that page, make sure the value for "Path prefix language code" is "blank".
https://drupal.stackexchange.com/questions/163754/how-to-show-registration-page-in-another-language-in-drupal-7-maintaining-its-n
For multi-language usage of CMS, they translate terms by a function similar to
function __($word) {
include 'fr.php';
if(!empty($lang[$word])) {$translated=$lang[$word];
} else {
$translated = $word;
}
return $translated;
}
Since we need to use this function several times in a php page, as all words and phrases will be echoed by __(' '); does the function need to include the language time every time, or it will be cached for the function after first load?
Since the language file contains a complete list of words and phrased used throughout the website (thousands of key/value), pho needs to load this long array into memory every time a page is visited. Is it really the best approach to add multi-language feature to a CMS?
If you can't use gettext() for some reason, you'd be better off, with something like this, to put it into an object with the included language strings as a static array, something like:
class Message {
private static $_messages = array();
public static function setMessageLibrary($sMessageLibrary) {
require_once $sMessageLibrary;
self::$_messages = $aMsgs;
}
public static function getMessage($sMessageId) {
return isset(self::$_messages[$sMessageId]) ? self::$_messages[$sMessageId] : "";
}
}
Your message library file (included with the setMessageLibrary() static function), of which you'll have one per language, will need a variable in it called $aMsgs which might look something like:
// Messages for fr-FR
$aMsgs = array(
'hello_everybody' => "Bonjour tout le monde"
...
and so on
);
Since it's all static but within the object you can effectively cache that included language file by setting it at the start of your script.
<?php
Message::setMessageLibrary('/lang/fr-FR/messages.inc.php');
echo Message::getMessage('hello_world');
echo Message::getMessage('another_message');
echo Message::getMessage('yet_another_message');
?>
All three messages will then reference the single language array stored in Message::$_messages
There's no sanitisation, nor sanity checks in there, but that's the basic principle anyway ... if you can't use gettext() ;)
1) it won't be cached, use include_once instead
2) no, i think gettext is doing it another/better way
IIRC, it will do some caching.
No, it's not. Check out gettext.
I am doing a PHP web site, without using any framework. I need that the site is available in several languages, and I was reading about it and it seems to be a little bit confusing. There are several solutions but all seem to depend on a specific framework.
What you think of using a simple translation function like the one shown below?
I mean, I would like to know what can be a disadvantage of using such code.
Here it is (this is just a simple and incomplete sample):
class Translator{
private $translations;
public function __construct(){
$this->translations = array(
'Inbox' => array(
'en' => 'Inbox',
'fr' => 'the french word for this'
),
'Messages' => array(
'en' => 'Messages',
'fr' => 'the french word for this'
)
//And so on...
);
}
public function translate($word,$lang){
echo $this->translations[$word][$lang];
}
}
It does not look bad. I've seen this used many times.
I would however separate the different strings in one file per language. At least, or if the files get large, one file per module per language.
Then your translation class can load and cache the language files (if you don't rely on any other caching system) every time a new language is to be used.
A little example of what i mean
class Translator {
private $lang = array();
private function findString($str,$lang) {
if (array_key_exists($str, $this->lang[$lang])) {
return $this->lang[$lang][$str];
}
return $str;
}
private function splitStrings($str) {
return explode('=',trim($str));
}
public function __($str,$lang) {
if (!array_key_exists($lang, $this->lang)) {
if (file_exists($lang.'.txt')) {
$strings = array_map(array($this,'splitStrings'),file($lang.'.txt'));
foreach ($strings as $k => $v) {
$this->lang[$lang][$v[0]] = $v[1];
}
return $this->findString($str, $lang);
}
else {
return $str;
}
}
else {
return $this->findString($str, $lang);
}
}
}
This will look for .txt files named after the language having entries such as this
Foo=FOO
Bar=BAR
It always falls back to the original string in case it does not find any translation.
It's a very simple example. But there is nothing wrong in my opinion with doing this by yourself if you have no need for a bigger framework.
To use it in a much simpler way you can always do this and create a file called 'EN_Example.txt'
class Example extends Translator {
private $lang = 'EN';
private $package = 'Example';
public function __($str) {
return parent::__($str, $this->lang . '_' . $this->package);
}
}
Sometimes you wish to translate strings that contain variables. One such approach is this which i find simple enough to use from time to time.
// Translate string "Fox=FOX %s %s"
$e = new Example();
// Translated string with substituted arguments
$s = printf($e->__('Fox'),'arg 1','arg 2');
To further integrate variable substitution the printf functionality can be put inside the __() function like this
public function __() {
if (func_num_args() < 1) {
return false;
}
$args = func_get_args();
$str = array_shift($args);
if (count($args)) {
return vsprintf(parent::__($str, $this->lang . '_' . $this->package),$args);
}
else {
return parent::__($str, $this->lang . '_' . $this->package);
}
}
There are a few things it appears you haven't considered:
Are you simply translating single words? What about sentence structure and syntax that differs between languages?
What do you do when a word or sentence hasn't been translated into a language yet?
Does your translations support variables? The order of words in a sentence can differ in different languages, and if you have a variable it usually won't be good enough simply to split the word around the sentence.
There are a two solutions that I've used and would recommend for PHP:
gettext - well supported in multiple languages
intsmarty - based on Smarty templates
The advantage with using a class or functions for this is that you can change the storage of the languages as the project grows. If you only have a few strings, there is absolutely no problems with your solution.
If you have a lot of strings it could take time, memory and harddrive resources to load the language arrays on all page loads. Then you probably want to split it up to different files, or maybe even use a database backend. If using i database, consider using caching (for example memcached) so you don't need to query the database hundreds of times with every page load.
You can also check out gettext which uses precompiled language files which are really fast.
I'd have thought it might be easier to simply use an include for each language, the contents of which could simply be a list of defines.
By doing this, you'd avoid both the overhead of including all the language data and the overhead of calling your 'translate' function on a regular basis.
Then again, this approach will limit things in terms of future flexability. (This may not be a factor though.)
It's fine to not use a framework. The only problem I see with your function is that it's loading a lot of data into memory. I would recommend having arrays for each language, that way you would only need to load the language that is being used.
Is using constants (defines) a bad practice?
That's how I have it setup. It was just to have multi langua support.
I have one portuguese file and an english files filled with:
define('CONST','Meaning');
Maybe this is a bit a memory hog, but I can access from every where I want :)
I may change to a oop approach, but for now I have this.
When I had a problem like this (but for a very small site, just a few pages) a long time ago, I created a file named langpack.php and any string of text on my site had to be run through that. Now, I would use a similar approach, but split over multiple files.
Example OOP Approach
langpack.php
abstract class langpack {
public static $language = array();
public static function get($n) {
return isset(self::$language[$n]) ? self::$language[$n] : null;
}
}
english.php
final class English extends langpack {
public static $language = array(
'inbox' => 'Inbox',
'messages' => 'Messages',
'downloadError' => 'There was an error downloading your files',
);
}
french.php
final class French extends langpack {
public static $language = array(
'inbox' => 'Inbioux',
'messages' => 'Omelette du Fromage',
'downloadError' => 'C\'est la vie',
);
}
You should get the idea from there. Implement an autoloader in a config file and then loading the language should be something you could easily do from the session, URL, or whatever, by using PHP's variable nature in conjunction with class instantiation, something like this:
$langpack = new $_SESSION['language'];
echo $langpack::get('inbox');
Of course, all this could be done with simple arrays, and accessed in an imperative style (with absolute references handled via $GLOBALS) to reduce some overhead and perhaps even make the mechanisms by which this is all handled a bit more transparent, but hey, that wouldn't be very OO, would it?
One could also be using the Symfony translation component, no framework is required and composer helps dealing with dependencies:
composer install --prefer-dist "symfony/translation":"#stable"
I think that's ok if you're not using any framework for other reasons. We've been in the same scenario as yours, when you cannot/don't want to use a more structured translation framework:
We were working at a small PHP project and where looking for some simple translation mechanism. We used an array approach similar to yours, but with separate files for each language texts. We put up a small component to keep thins as clean as possible.
If you want to give a look, we shared that on https://github.com/BrainCrumbz/simple-php-translate. Please feel free to improve it!
i would simply use a function with controller and language inputs, for instance:
function getLanguageFile($controller, $lang){
// security: only allow letters a-z in both strings
$controller = preg_replace('/([^a-z]*)/', '', $controller);
$lang = preg_replace('/([^a-z]*)/', '', $lang);
// return language details if present on disk
if (is_file('lang/'.$controller.'/'.$lang.'.json')){
return json_decode(file_get_contents('lang/'.$controller.'/'.$lang.'.json'));
}
return false;
}
you simply have to place your json formatted strings in lang/index/en.json if controller is index and language is en.
you could add a function for dependencies (for instance you want to load index controller values on access to another controller) all you have to do is to merge the results.
you could simply include php files with arrays aswell and just return the array then, but i suggest you split these translations in larger projects. if your project isn't that big, your function is absolutely ok.
Questions Updated instead of making a new question...
I really want to provide a few alternative languages other then English on my social network site I am building, this will be my first time doing any kind of language translation so please bear with me.
I am researching so I am al ear and open to ideas and I have a lot already here is are the questions.
1)
What does i18n mean, I see it often when researching language translation on SO?
2)
Most people say use gettext PHP has an extension or support for it,
well I have been researching it and I have a basic understanding of it, as far as I can tell it is a lot of extra work to go this route,
I mean coding my site to use it's functions ie; _('hello world i'm in English for now') or else gettext('hello world i'm in English for now') is no problem as any route I go will require that.
But then you have to install gettext on your server and get it working,
then use some special editors to create special files and compile them I think?
Sounds like a pain, I understand this is supposed to be the best route to go though, well everyone seems to say it is.
So can someone tell me why this is the route to go?
3)
I really like the simplicity of this approach, just building a language array and calling the phrase you need in a function like the example below
, you would then just include a file with the appropriate language array.
What I really want to know is, would this be the less better performance method on a high traffic and fairly large site compared to using gettext and if so can you explain why please?
<?PHP
//Have seperate language files for each language I add, this would be english file
function lang($phrase){
static $lang = array(
'NO_PHOTO' => 'No photo\'s available',
'NEW_MEMBER' => 'This user is new'
);
return $lang[$phrase];
}
//Then in application where there is text from the site and not from users I would do something like this
echo lang('NO_PHOTO'); // No photo's available would show here
?>
* some code used from brianreavis's answer below
It'd probably be best to define a function that handles your language mapping. That way, if you do want to change how it works later, you're not forced to scour hundreds of scripts for cases where you used $lang[...] and replace them with something else.
Something like this would work and would be nice & fast:
function lang($phrase){
static $lang = array(
'NO_PHOTO' => 'No photo\'s available',
'NEW_MEMBER' => 'This user is new'
);
return $lang[$phrase];
}
Make sure the array is declared static inside the function so it doesn't get reallocated each time the function is called. This is especially important when $lang is really large.
To use it:
echo lang('NO_PHOTO');
For handling multiple languages, just have this function defined in multiple files (like en.php, fr.php, etc) and require() the appropriate one for the user.
This might work better:
function _L($phrase){
static $_L = array(
'NO_PHOTO' => 'No photo\'s available',
'NEW_MEMBER' => 'This user is new'
);
return (!array_key_exists($phrase,$_L)) ? $phrase : $_L[$phrase];
}
Thats what i use for now. If the language is not found, it will return the phrase, instead of an error.
You should note that an array can contain no more than ~65500 items. Should be enough but well, just saying.
Here's some code that i use to check for the user's language:
<?php
function setSessionLanguageToDefault() {
$ip=$_SERVER['REMOTE_ADDR'];
$url='http://api.hostip.info/get_html.php?ip='.$ip;
$data=file_get_contents($url);
$s=explode (':',$data);
$s2=explode('(',$s[1]);
$country=str_replace(')','',substr($s2[1], 0, 3));
if ($country=='us') {
$country='en';
}
$country=strtolower(ereg_replace("[^A-Za-z0-9]", "", $country ));
$_SESSION["_LANGUAGE"]=$country;
}
if (!isset($_SESSION["_LANGUAGE"])) {
setSessionLanguageToDefault();
}
if (file_exists(APP_DIR.'/language/'.$_SESSION["_LANGUAGE"].'.php')) {
include(APP_DIR.'/language/'.$_SESSION["_LANGUAGE"].'.php');
} else {
include(APP_DIR.'/language/'.DEFAULT_LANG.'.php');
}
?>
Its not done yet, but well i think this might help a lot.
Don't write your own language framework. Use gettext. PHP has standard bindings that you can install.
Don't reinvent the wheel. Use for example gettext or Zend_Translate.
As the other answers don't really answer all the questions, I will go for that in my answer plus offering a sensible alternative.
1)
I18n is short for Internationalization and has some similarities to I-eighteen-n.
2)
In my honest opinion gettext is a waste of time.
3)
Your approach looks good. What you should look for are language variables. The WoltLab Community Framework 2.0 implements a two-way language system. For once there are language variables that are saved in database and inside a template one only uses the name of the variable which will then be replaced with the content of the variable in the current language (if available). The second part of the system provides a way to save user generated content in multiple languages (input in multiple languages required).
Basically you have the interface text that is defined by the developer and the content that is defined by the user. The multilingual text of the content is saved in language variables and the name of the language variable is then used as value for the text field in the specific content table (as single-language contents are also possible).
The structure of the WCF is sadly in a way that reusing code outside of the framework is very difficult but you can use it as inspiration. The scope of the system depends solely on what you want to achieve with your site. If it is going to be big than you should definitely take a look at the WCF system. If it's small a few dedicated language files (de.php, en.php, etc), from which the correct one for the current language is included, will do.
why not you just make it as multi-dimesional array...such as this
<?php
$lang = array(
'EN'=> array(
'NO_PHOTO'=>'No photo\'s avaiable',
'NEW_MEMBER'=>'This user is new',
),
'MY'=> array(
'NO_PHOTO'=>'Tiada gambar',
'NEW_MEMBER'=>'Ini adalah pengguna baru',
)
);
?>
You can do this:
class T {
const language = "English";
const home = "Home";
const blog = "Blog";
const forum = "Forum";
const contact = "Support";
}
You would have a file like this for each language. To use the text:
There is no place like <?=T::home?>.
The downside is that if you add a new constant, you have to do it for every langauge file. If you forget one, your page breaks for that language. That is a bit nasty, but it is efficient since it doesn't need to create a large associative array and possibly the values even get inlined.
Maybe access could be improved, eg:
class T {
const home = "home";
public static function _ ($name) {
$value = #constant("self::$name");
return $value ? $value : $name;
}
// Or maybe through an instance:
public function __get ($name) {
$value = #constant("self::$name");
return $value ? $value : $name;
}
}
echo "There is no " . T::_("place") . " like " . T::_("home");
$T = new T();
echo "There is no " . $T->place . " like " . $T->home;
We still avoid the array and rely on constant to do the lookup, which I assume is more expensive than using the constants directly. The up side is the lookup can use a fallback when the key is not found.
An extension to the answers above whom deserve the credit - I'm just posting as maybe this will also be useful to someone else who ends up here.
I personally prefer the key to the array to be the actual phrase in my mother tongue (in my case English) rather than a CONSTANT_VALUE because:
I find it easier to read the code when the text is in my native language rather than having to remember what a CONSTANT_VALUE actually outputs
It means no lookup is needed to return the phrase for visitors who also use my naitive language (giving marginally better performance)
It's one less list of values to maintain
The downside is that it's harder to spot missing values in other languages as you don't necessarily have a master list anymore - I also log a warning from the abstract method so that I spot any missing values.
I implemented as:
An abstract class with static methods for outputting the text value (using late static binding)
A concrete class for each language: English overriding the method to return the phrase without translation, other languages overriding the list of phrases so that a translated phrase is returned
<?php
namespace Language;
abstract class _Language
{
protected static $displayText = array();
public static function output($phrase){
return static::$displayText[$phrase] ?? $phrase;
}
}
<?php
namespace Language;
class English extends _Language
{
public static function output($phrase){
return $phrase;
}
}
<?php
namespace Language;
class Spanish extends _Language
{
protected static $displayText = array(
'Forename' => 'Nombre',
'Registered Email' => 'Correo electrónico registrado',
'Surname' => 'Apellido'
);
}
Usage:
$language = new \Language\Spanish();
echo $language::output('Forename'); // Outputs: Nombre
$language = new \Language\English();
echo $language::output('Registered Email'); // Outputs: Registered Email
Unfortunately gettext not work good and have problems in various situation like on different OS (Windows or Linux) and make it work is very difficult.
In addition it require you set lot's of environment variables and domains and this not have any sense.
If a developer want simply get the translation of a text he should only set the .mo file path and get the translation with one function like translate("hello","en_EN"); With gettext this is not possible.