Sito web Multilingua con Switch e link concatenanti

  • Creatore Discussione Creatore Discussione Jonn
  • Data di inizio Data di inizio

Jonn

Utente Attivo
29 Dic 2012
352
3
18
Pistoia
Salve a tutti,
sto lavorando ad un sito multilingua, solo che non voglio utilizzare CMS già pronti quindi mi sto dilettando nella gestione delle variabili (diciamo così) con PHP (versione 5.3 che ho nel server)

ho fatto un lavoro di questo tipo finora:
ho creato tutta la struttura estetica, ho inserito poi nel content uno switch che prende la variabile (lang) dai link della lingua (immagini con link)
e fa il confronto con uno switch per l'output:

Links per la scelta della lingua (che è una position statica che si trova in tutte le pagine):
HTML:
<a href="?lang=Italiano"><img style="border:1px solid black;" src="immagini/bandierine/Italian.gif" alt="Italiano" /></a>
<a href="?lang=Russian"><img style="border:1px solid black;" src="immagini/bandierine/Russian.gif" alt="Russian" /></a>
<a href="?lang=Deutsch"><img style="border:1px solid black;" src="immagini/bandierine/Deutsch.gif" alt="Deutsch" /></a>
<a href="?lang=English"><img style="border:1px solid black;" src="immagini/bandierine/English.gif" alt="English" /></a>

Quando viene cliccato uno dei link, tramite il GET e SWITCH in PHP richiamati da 2 positions, cambia sia il menu che il content e vengono richiamati entrambi quindi nella lingua richiesta:

1° il modulo navleft:
PHP:
<?php	if (isset($_GET['lang'])) 
						{
							switch($_GET['lang'])
							{
							case "Italiano"         : include('navleft_1_menu_ita.php');break;
							case "English"          : include('navleft_1_menu_eng.php');break;
							case "Deutsch"          : include('navleft_1_menu_deu.php');break;
							case "Russian"          : include('navleft_1_menu_rus.php');break;
							}
						}
						else{include('navleft_1_menu_ita.php');}
				?>

2° la position content (che sarebbe il corpo pagina):
PHP:
<?php
	if (isset($_GET['lang']))
						{
							switch($_GET['lang'])
							{
							case "Italiano"         : include('switch_content_ita.php');break;
							case "English"          : include('switch_content_eng.php');break;
							case "Deutsch"          : include('switch_content_deu.php');break;
							case "Russian"          : include('switch_content_rus.php');break;
							}
						}
						else{include('homepage_ita.php');}
				?>

E fino a qui ci siamo...
Ora analizziamo un secondo il codice del menu che viene richiamato in navleft (ne inserisco uno solo tanto l'unica cosa che cambia rispetto agli altri è la dicitura "ita", "eng", "deu" e "rus":
HTML:
<div class="navleft_mod">
	<div class="navleft_mod_head"><b>Menu Italiano</b></div>
	<div class="navleft_mod_body"><i>
		  <a href="?lang=Italiano&amp;pagina=about_ita"><img src="immagini/moduli/list_menu.png" alt="" /> Home</a>
		<br /><a href="?pagina=nome"><img src="immagini/moduli/list_menu.png" alt="" /> Servizi</a>
		<br /><a href="?pagina=nome"><img src="immagini/moduli/list_menu.png" alt="" /> Men&ugrave;</a>
		<br /><a href="?pagina=nome"><img src="immagini/moduli/list_menu.png" alt="" /> Galleria Fotografica</a>
		<br /><a href="?pagina=nome"><img src="immagini/moduli/list_menu.png" alt="" /> Eventi e Aggiornamenti</a>
		<br /><a href="?pagina=nome"><img src="immagini/moduli/list_menu.png" alt="" /> Dove Siamo</a>
		<br /><a href="?pagina=nome"><img src="immagini/moduli/list_menu.png" alt="" /> Contattaci</a></i>
	</div>
</div>

Ho scritto solo il primo link perchè tanto poi risolto quello, risolvo tutti gli altri di conseguenza.
Come si può vedere nel link ho dichiarato un'altra variabile che viene switchata dal menu.
Il problema è che se io non dichiaro nel link che la variabile lang è settata su Italiano, quest'ultimo (il link) mi rimanda direttamente alla pagina iniziale come se entrambe le variabili (lang e pagina) non fossero settate.
Settando invece la variabile nel link, il tutto funziona alla perfezione e teoricamente il mio sistema multilingua è già funzionante, volevo sapere più che altro se secondo voi questa può essere una soluzione valida oppure no.
Questo perchè non so bene come gestire le variabili, l'idea che avevo in mente era, che di default il sito si apre in italiano (e funziona) poi però quando viene scelta una lingua, viene settata una variabile (lang) che rimane sempre quella, e le pagine fanno riferimento a questa variabile senza doverla sempre dichiarare in tutti i link, fino a che non viene scelta un'altra lingua.
Spero di essere stato chiaro e preciso, grazie in anticipo a tutti.
 
Secondo me è sbagliato il tuo approccio al problema. Invece di includere file diversi per ogni lingua, dovresti semplicemente includere traduzioni diverse. Altrimenti ogni volta che cambi qualcosa nel sito devi cambiarlo per ogni traduzione, e PHP diventa fondamentalmente inutile.

Ti posto una classe che ho creato qualche tempo fa per un sito Web piuttosto semplice:
PHP:
<?php

class I18n
{ 
    /**
     * @var string Path alle lingue
     */
    protected $languagesPath = null;

    /**
     * @var array|null Lingue disponibili
     */
    protected $availableLanguages = null;

    /**
     * @var string Lingua dell'utente
     */
    protected $language = null;

    /**
     * @var array Traduzioni delle lingue
     */
    protected $translations = array();

    /**
     * Crea una nuova istanza della classe.
     *
     * @param string $languagesPath   Path alle lingue
     * 
     * @return I18n
     *
     * @throws \InvalidArgumentException Se il path alle lingue non esiste
     */
    public function __construct($languagesPath)
    {
        if (!is_dir($languagesPath)) {
            throw new \InvalidArgumentException(sprintf(
                'Impossibile trovare le lingue in "%s"',
                $languagesPath
            ));
        }

        $this->languagesPath   = $languagesPath;

        $this->getAvailableLanguages();
    }

    /**
     * Restituisce il path alle lingue.
     *
     * @return string
     */
    public function getLanguagesPath()
    {
        return $this->languagesPath;
    }

    /**
     * Restituisce le lingue disponibili.
     *
     * @param boolean $forceReload Ricaricare tutti i file?
     *
     * @return array
     */
    public function getAvailableLanguages($forceReload = false)
    {
        if (null === $this->availableLanguages || $forceReload) {
            foreach (glob("{$this->languagesPath}/*.php") as $file) {
                $this->availableLanguages[] = basename($file, '.php');
            }
        }

        return $this->availableLanguages;
    }

    /**
     * Determina se la lingua specificata è disponibile.
     *
     * @param string $language Lingua
     *
     * @return boolean
     */
    public function isLanguageAvailable($language)
    {
        return in_array($language, $this->getAvailableLanguages(), true);
    }

    /**
     * Imposta la lingua attuale.
     *
     * @param string $language Lingua
     *
     * @throws \InvalidArgumentException Se la lingua non esiste
     */
    public function setLanguage($language)
    {
        try {
            $this->checkLanguage($language);
        } catch (\InvalidArgumentException $e) {
            throw $e;
        }

        $this->language = $language;
    }

    /**
     * Restituisce la lingua dell'utente.
     * 
     * @return string
     */
    public function getLanguage()
    {
        return $this->language;
    }

    /**
     * Traduce la chiave specificata.
     *
     * @param string      $key      Chiave di traduzione
     * @param array       $params   Parametri
     * @param string|null $language Lingua (quella dell'utente di default)
     *
     * @return string Traduzione
     *
     * @throws \InvalidArgumentException Se la traduzione non esiste
     */
    public function translate($key, $params = array(), $language = null)
    {
        $language = (null !== $language) ? $language : $this->language;

        try {
            $this->checkLanguage($language);
        } catch (\InvalidArgumentException $e) {
            throw $e;
        }

        $this->loadLanguage($language);

        if (!isset($this->translations[$key])) {
            throw new \InvalidArgumentException(sprintf(
                'Impossibile trovare la traduzione "%s" per la lingua "%s"',
                $key,
                $language
            ));
        }

        $translation = $this->translations[$key];

        foreach ($params as $key => $value) {
            $translation = str_replace($translation, "%{{$key}}", $value);
        }

        return $translation;
    }

    /**
     * Carica la lingua specificata.
     *
     * @param string  $language    Lingua
     * @param boolean $forceReload Ricaricare i file?
     *
     * @return array Chiavi di traduzione
     *
     * @throws \InvalidArgumentException Se la lingua non è disponibile
     */
    public function loadLanguage($language, $forceReload = false)
    {
        try {
            $this->checkLanguage($language);
        } catch (\InvalidArgumentException $e) {
            throw $e;
        }

        if (!isset($this->translations[$language]) || $forceReload) {
            $this->translations[$language] = require "{$this->languagesPath}/{$language}.php";
        }

        return $this->translations[$language];
    }

    /**
     * Testa la validità della lingua.
     *
     * @param string $language Lingua
     *
     * @throws \InvalidArgumentException Se la lingua non è disponibile
     */
    protected function checkLanguage($language)
    {
        if (!$this->isLanguageAvailable($language)) {
            throw new \InvalidArgumentException(sprintf(
                'La lingua "%s" non è valida (disponibili: %s)',
                $language,
                implode(', ', $this->getAvailableLanguages())
            ));
        }
    }
}
Considera che in questo momento non posso testarla perché non sviluppo in PHP da un po' e ho cambiato alcune cose, quindi assicurati che funzioni.

È da usare in questo modo: crea una cartella languages in cui metterai i file di lingua in questo formato:
PHP:
<?php
return array(
    'key' => 'ciao',
    'key_2' => 'traduzione con %{param}',
    // ...
);

E nella tua index.php istanzia la classe così:
PHP:
<?php

require_once 'I18n.php';

// helper per le traduzioni
function t($key, $params = array(), $language = null)
{
    // evitare le variabili globali in generale: qui semplificano molto le cose,
    // quindi sono un male necessario
    global $i18n;

    return $i18n->t($key, $params, $language);
}

$i18n = new I18n(__DIR__ . '/languages');

// imposta eventualmente una lingua di default
if (!isset($_COOKIE['language'])) {
    $language = 'it';
}

if (isset($_GET['language'])) { // se l'utente vuole cambiare lingua...
    // ...imposta la nuova lingua
    $language = $_GET['language'];
} elseif (isset($_COOKIE['lanugage'])) { // se l'utente ha già una lingua...
    // ...usa la lingua preferita
    $language = $_COOKIE['language'];
} else { // se è la prima visita (o il cookie è scaduto)...
    // ...usa la lingua di default
    $language = 'it';
}

try {
    // prova a impostare la lingua dell'utente
    $i18n->setLanguage($language);
} catch (\InvalidArgumentException $e) {
    // se non esiste (tentativo di manomissione?) imposta quella di default
    $i18n->setLanguage('it');
}

// a ogni richiesta reimposta il cookie per un anno
setcookie('language', $i18n->getLanguage(), time() + 31536000);

Adesso puoi scrivere le traduzioni in questo modo:
PHP:
<?php
echo t('key'); // ciao
echo t('key_2', array('param' => 'parametri')); // traduzione con parametri

Direi che non è male, no? :)
 
Ultima modifica:
Bene,
diciamo che hai abbattuto il mio castellino di carte con un bazooka!
Comunque la tua soluzione mi sembra molto professionale, mi sono messo a studiarla per capire bene come funzioni visto che ho molte poche nozioni di PHP.
Considero comunque il 3D risolto!
Grazie mille Alessandro ;)
 
Ti dirò, il fatto è che la tua soluzione mi sembra più che soddisfacente perchè ho capito a grandi linee come funziona.
solo che secondo me una soluzione più semplice esiste, più che altro perchè è un sito completamente statico quindi gli eventuali cambiamenti li apporto io.
La tua soluzione me la studio comunque, ma pensavo a come dichiarare una variabile vuota ($lang), poi tramite il get questa variabile assume un valore (passato dai links, per esempio il valore 'English') che rimane sempre memorizzato durante la navigazione nel menu fino a quando viene selezionato un altro link che cambia il valore (da English per esempio a Italiano) e così via.
Però ecco, con le poche nozioni che ho in php è come se mi avessi scaricato addosso la Treccani 2013 da studiare per un sito che non ha pretese particolari. XD
 
Mi pare ottimale la soluzione di alessandro ma un po troppo complessa per piccoli/medi progetti secondo me. Io di solito uso un approccio col json per il multilingua, mettendo la traduzione in unico file sotto diverse chiavi, del tipo
Codice:
{
    "ita": {
      //etc...
    },
    "eng": {
      //etc...
    },
    "fr": {
      //etc....
    }
//etc...
}
In questo modo posso avere anche un accesso diretto da js senza risposta dal php
es.
Codice:
//lang sarà una variabile js che prende il valore dal document.cookie o viene settata prima dal php
$.getJSON('lang.json').done(function(data){ alert(data[lang].key); });

E da php ovviamente verrà gestito come array con json_decode

Pro:
Hai un solo file da gestire e puoi utilizzarlo anche senza php
Contro:
Se si hanno molte chiavi puo diventare un po pesante, in questo caso è meglio un metodo con file singoli

Infine 2 considerazioni sulla classe di alessandro:
Secondo me era meglio se facevi le funzioni statiche per evitare nuove istanze e global
Per i parametri nelle traduzioni avrei usato vsprintf()
 
Interessante la soluzione con JSON soprattutto perché permette di accedere alle traduzioni tramite Javascript. Non ne ho mai avuto bisogno quindi non mi sono mai posto il problema.

A rendere i metodi statici avevo pensato, ma non mi sembrava corretto sotto un punto di vista architetturale. Per di più l'uso della funzione helper t() permette di centralizzare il processo di traduzione. In questo modo è semplice modificare la logica dell'applicazione ed eliminare la variabile globale. Diciamo che in un'applicazione professionale l'approccio migliore è l'iniezione delle dipendenze.

Interessante la funzione vsprintf(), non la conoscevo proprio. Del resto ormai è tardi: ho smesso di sviluppare in PHP da un po'. :)
 

Discussioni simili