Sito Web multilingue di buone pratiche


179

Sono stato alle prese con questa domanda per alcuni mesi ormai, ma non sono stato in una situazione che avevo bisogno di esplorare tutte le possibili opzioni prima. In questo momento, sento che è tempo di conoscere le possibilità e creare le mie preferenze personali da utilizzare nei miei prossimi progetti.

Vorrei prima fare uno schizzo della situazione che sto cercando

Sto per aggiornare / riqualificare un sistema di gestione dei contenuti che utilizzo da un po 'di tempo. Tuttavia, ritengo che il multi-linguaggio sia un grande miglioramento di questo sistema. Prima non ho usato alcun framework ma ho intenzione di usare Laraval4 per il prossimo progetto. Laravel sembra la scelta migliore di un modo più pulito per codificare PHP. Sidenote: Laraval4 should be no factor in your answer. Sto cercando modi generali di traduzione indipendenti dalla piattaforma / dal framework.

Cosa dovrebbe essere tradotto

Poiché il sistema che sto cercando deve essere il più intuitivo possibile, il metodo di gestione della traduzione dovrebbe essere all'interno del CMS. Non dovrebbe essere necessario avviare una connessione FTP per modificare i file di traduzione o qualsiasi modello analizzato html / php.

Inoltre, sto cercando il modo più semplice per tradurre più tabelle di database forse senza la necessità di creare tabelle aggiuntive.

Cosa mi è venuto in mente

Come ho già cercato, letto e provato le cose da solo. Ci sono un paio di opzioni che ho. Ma ancora non mi sento come se avessi raggiunto un metodo di buone pratiche per quello che sto veramente cercando. In questo momento, questo è quello che ho escogitato, ma questo metodo ha anche effetti collaterali.

  1. PHP Parsed Templates : il sistema di template dovrebbe essere analizzato da PHP. In questo modo sono in grado di inserire i parametri tradotti nell'HTML senza dover aprire i modelli e modificarli. Oltre a ciò, i template analizzati PHP mi danno la possibilità di avere 1 template per il sito web completo invece di avere una sottocartella per ogni lingua (che ho avuto prima). Il metodo per raggiungere questo obiettivo può essere Smarty, TemplatePower, Laravel's Blade o qualsiasi altro parser di template. Come ho già detto, questo dovrebbe essere indipendente dalla soluzione scritta.
  2. Database Driven : forse non ho bisogno di menzionarlo di nuovo. Ma la soluzione dovrebbe essere guidata dal database. Il CMS ha lo scopo di essere orientato agli oggetti e MVC, quindi dovrei pensare a una struttura di dati logica per le stringhe. Come i miei modelli sarebbero stati strutturati: templates / Controller / view.php forse tale struttura avrebbe più senso: Controller.View.parameter. La tabella del database avrebbe questi campi a lungo con un valuecampo. All'interno dei template potremmo usare alcuni metodi di ordinamento come echo __('Controller.View.welcome', array('name', 'Joshua'))e il parametro contiene Welcome, :name. Quindi il risultato è Welcome, Joshua. Questo sembra un buon modo per farlo, perché i parametri come: name sono facili da capire dall'editor.
  3. Basso carico del database : ovviamente il sistema sopra riportato causerebbe un carico del carico del database se queste stringhe vengono caricate in movimento. Pertanto, avrei bisogno di un sistema di memorizzazione nella cache che esegua nuovamente il rendering dei file della lingua non appena vengono modificati / salvati nell'ambiente di amministrazione. Poiché i file vengono generati, è necessario anche un buon layout del file system. Immagino che possiamo andare con languages/en_EN/Controller/View.phpo .ini, qualunque cosa ti si adatti meglio. Forse un .ini viene persino analizzato più velocemente alla fine. Questo dovrebbe contenere i dati nel file format parameter=value; . Immagino che questo sia il modo migliore per farlo, dal momento che ogni vista renderizzata può includere il proprio file di lingua se esiste. Quindi i parametri della lingua dovrebbero essere caricati in una vista specifica e non in un ambito globale per evitare che i parametri si sovrascrivano l'un l'altro.
  4. Traduzione della tabella del database : questa è la cosa di cui mi preoccupo di più. Sto cercando un modo per creare traduzioni di notizie / pagine / ecc. il più rapidamente possibile. Avere due tabelle per ogni modulo (per esempio Newse News_translations) è un'opzione, ma è come lavorare molto per ottenere un buon sistema. Una delle cose che mi è venuta in mente è basata su un data versioningsistema che ho scritto: esiste un nome di tabella del database Translations, questa tabella ha una combinazione unica di language, tablenameeprimarykey. Ad esempio: en_En / News / 1 (riferito alla versione inglese della notizia con ID = 1). Ma ci sono 2 enormi svantaggi di questo metodo: prima di tutto questa tabella tende a diventare piuttosto lunga con molti dati nel database e in secondo luogo sarebbe un lavoro infernale usare questa configurazione per cercare la tabella. Ad esempio, la ricerca della lumaca SEO dell'articolo sarebbe una ricerca full-text, che è piuttosto stupida. Ma d'altra parte: è un modo rapido per creare contenuti traducibili in ogni tabella molto velocemente, ma non credo che questo professionista superi le truffe.
  5. Lavoro front-end : anche il front-end avrebbe bisogno di un pensiero. Ovviamente archiviamo le lingue disponibili in un database e (de) attiviamo quelle di cui abbiamo bisogno. In questo modo lo script può generare un menu a discesa per selezionare una lingua e il back-end può decidere automaticamente quali traduzioni possono essere fatte usando il CMS. La lingua scelta (ad es. En_EN) verrebbe quindi utilizzata quando si ottiene il file della lingua per una vista o per ottenere la traduzione corretta per un elemento di contenuto sul sito Web.

Quindi eccoli. Le mie idee finora. Non includono ancora opzioni di localizzazione per date, ecc., Ma poiché il mio server supporta PHP5.3.2 + l'opzione migliore è usare l'estensione intl come spiegato qui: http://devzone.zend.com/1500/internationalization-in -php-53 / - ma questo sarebbe utile in qualsiasi stadio di sviluppo successivo. Per ora il problema principale è come avere le migliori pratiche di traduzione del contenuto in un sito Web.

Oltre a tutto ciò che ho spiegato qui, ho ancora un'altra cosa che non ho ancora deciso, sembra una domanda semplice, ma in realtà mi sta dando mal di testa:

Traduzione URL? Dovremmo farlo o no? e in che modo?

Quindi .. se ho questo url: http://www.domain.com/about-use l'inglese è la mia lingua predefinita. Dovrei tradurre questo URL http://www.domain.com/over-onsquando scelgo l'olandese come lingua? O dovremmo seguire la strada facile e cambiare semplicemente il contenuto della pagina visibile su /about. L'ultima cosa non sembra un'opzione valida perché genererebbe più versioni dello stesso URL, l'indicizzazione del contenuto fallirà nel modo giusto.

Un'altra opzione sta usando http://www.domain.com/nl/about-usinvece. Questo genera almeno un URL univoco per ciascun contenuto. Inoltre, sarebbe più semplice andare in un'altra lingua, ad esempio, http://www.domain.com/en/about-use l'URL fornito è più facile da capire sia per i visitatori di Google che per quelli umani. Usando questa opzione, cosa facciamo con le lingue predefinite? La lingua predefinita dovrebbe rimuovere la lingua selezionata per impostazione predefinita? Quindi reindirizzare http://www.domain.com/en/about-usa http://www.domain.com/about-us... Ai miei occhi questa è la soluzione migliore, perché quando il CMS è impostato per una sola lingua non è necessario avere questa identificazione della lingua nell'URL.

E una terza opzione è una combinazione di entrambe le opzioni: usando "lingua-identificazione-less" -URL ( http://www.domain.com/about-us) per la lingua principale. E usa un URL con una lumaca SEO tradotta per le lingue secondarie: http://www.domain.com/nl/over-ons&http://www.domain.com/de/uber-uns

Spero che la mia domanda ti rompa la testa, hanno sicuramente rotto la mia! Mi ha già aiutato a risolvere le cose come una domanda qui. Mi ha dato la possibilità di rivedere i metodi che ho usato prima e l'idea che sto avendo per il mio CMS imminente.

Vorrei ringraziarvi già per aver dedicato del tempo a leggere questo mucchio di testo!

// Edit #1:

Ho dimenticato di menzionare: la funzione __ () è un alias per tradurre una determinata stringa. All'interno di questo metodo dovrebbe esserci ovviamente una sorta di metodo di fallback in cui viene caricato il testo predefinito quando non ci sono ancora traduzioni disponibili. Se la traduzione non è presente, deve essere inserita o il file di traduzione deve essere rigenerato.


Risposte:


115

Premessa dell'argomento

Esistono tre aspetti distinti in un sito multilingue:

  • traduzione dell'interfaccia
  • soddisfare
  • instradamento dell'URL

Mentre tutti sono interconnessi in modi diversi, dal punto di vista del CMS sono gestiti utilizzando diversi elementi dell'interfaccia utente e memorizzati in modo diverso. Sembri fiducioso nella tua implementazione e comprensione dei primi due. La domanda riguardava quest'ultimo aspetto: "Traduzione URL? Dobbiamo farlo o no? E in che modo?"

Di cosa può essere fatto l'URL?

Una cosa molto importante è, non esagerare con IDN . Preferisce invece la traslitterazione (anche: trascrizione e romanizzazione). Mentre a prima vista IDN sembra un'opzione praticabile per gli URL internazionali, in realtà non funziona come pubblicizzato per due motivi:

  • alcuni browser trasformeranno i caratteri non ASCII come 'ч'o 'ž'in '%D1%87'e'%C5%BE'
  • se l'utente ha temi personalizzati, è molto probabile che il carattere del tema non contenga simboli per quelle lettere

In realtà ho provato ad avvicinarmi a IDN qualche anno fa in un progetto basato su Yii (framework orribile, IMHO). Ho riscontrato entrambi i problemi sopra menzionati prima di eliminare la soluzione. Inoltre, sospetto che potrebbe essere un vettore di attacco.

Opzioni disponibili ... come le vedo io.

Fondamentalmente hai due scelte, che potrebbero essere astratte come:

  • http://site.tld/[:query]: dove [:query]determina la scelta della lingua e dei contenuti

  • http://site.tld/[:language]/[:query]: dove [:language]parte dell'URL definisce la scelta della lingua e [:query]viene utilizzata solo per identificare il contenuto

La query è Α e Ω ..

Diciamo che scegli http://site.tld/[:query].

In quel caso hai una fonte primaria di linguaggio: il contenuto del [:query]segmento; e due fonti aggiuntive:

  • valore $_COOKIE['lang']per quel particolare browser
  • elenco delle lingue in HTTP Accept-Language (1) , (2) un colpo di testa

Innanzitutto, devi abbinare la query a uno dei modelli di routing definiti (se la tua scelta è Laravel, leggi qui ). In caso di corrispondenza corretta del modello, è quindi necessario trovare la lingua.

Dovresti passare attraverso tutti i segmenti del modello. Trova le potenziali traduzioni per tutti quei segmenti e determina quale lingua è stata utilizzata. Le due fonti aggiuntive (cookie e intestazione) verrebbero utilizzate per risolvere i conflitti di routing, quando (non "se") si presentano.

Prendiamo ad esempio: http://site.tld/blog/novinka.

Questa è traslitterazione "блог, новинка", che in inglese significa approssimativamente "blog", "latest".

Come puoi già notare, in russo "блог" verrà traslitterato come "blog". Ciò significa che per la prima parte di [:query]te (nel migliore dei casi ) finirà con un ['en', 'ru']elenco di lingue possibili. Quindi prendi il prossimo segmento - "novinka". Che potrebbe avere una sola lingua nella lista delle possibilità: ['ru'].

Quando l'elenco contiene un elemento, hai trovato correttamente la lingua.

Ma se finisci con 2 (esempio: russo e ucraino) o più possibilità .. o 0 possibilità, a seconda dei casi. Dovrai utilizzare i cookie e / o l'intestazione per trovare l'opzione corretta.

E se tutto il resto fallisce, scegli la lingua predefinita del sito.

Lingua come parametro

L'alternativa è utilizzare l'URL, che può essere definito come http://site.tld/[:language]/[:query]. In questo caso, quando si traduce una query, non è necessario indovinare la lingua, perché a quel punto si sa già quale utilizzare.

Esiste anche una fonte secondaria di lingua: il valore del cookie. Ma qui non ha senso scherzare con l'intestazione Accept-Language, perché non hai a che fare con quantità sconosciute di lingue possibili in caso di "avvio a freddo" (quando l'utente apre per la prima volta il sito con una query personalizzata).

Invece hai 3 opzioni semplici e prioritarie:

  1. se il [:language]segmento è impostato, usalo
  2. se $_COOKIE['lang']è impostato, usalo
  3. usa la lingua predefinita

Quando si possiede la lingua, si tenta semplicemente di tradurre la query e, se la traduzione non riesce, utilizzare il "valore predefinito" per quel particolare segmento (in base ai risultati del routing).

Non è qui una terza opzione?

Sì, tecnicamente è possibile combinare entrambi gli approcci, ma che complicherebbe il processo e ospitare solo le persone che vogliono cambiare manualmente l'URL di http://site.tld/en/newsal http://site.tld/de/newse si aspettano la pagina di notizie per il cambiamento a Tedesco.

Ma anche questo caso potrebbe essere mitigato usando il valore del cookie (che conterrebbe informazioni sulla precedente scelta della lingua), da implementare con meno magia e speranza.

Quale approccio usare?

Come avrete già intuito, raccomanderei http://site.tld/[:language]/[:query]come l'opzione più sensata.

Anche in una situazione di parole reali avresti la terza parte principale nell'URL: "titolo". Come nel nome del prodotto nel negozio online o nel titolo dell'articolo nel sito di notizie.

Esempio: http://site.tld/en/news/article/121415/EU-as-global-reserve-currency

In questo caso '/news/article/121415'sarebbe la query e il 'EU-as-global-reserve-currency'titolo è. A puro scopo SEO.

Può essere fatto in Laravel?

Kinda, ma non di default.

Non ne ho molta familiarità, ma da quello che ho visto, Laravel utilizza un semplice meccanismo di routing basato su pattern. Per implementare URL multilingue dovrai probabilmente estendere le classi principali , perché il routing multilingue richiede l'accesso a diverse forme di archiviazione (database, cache e / o file di configurazione).

È indirizzato. E adesso?

Di conseguenza, si otterrebbero due preziose informazioni: la lingua corrente e i segmenti di query tradotti. Questi valori possono quindi essere utilizzati per inviare le classi che genereranno il risultato.

Fondamentalmente, il seguente URL: http://site.tld/ru/blog/novinka(o la versione senza '/ru') viene trasformato in qualcosa di simile

$parameters = [
   'language' => 'ru',
   'classname' => 'blog',
   'method' => 'latest',
];

Che usi solo per la spedizione:

$instance = new {$parameter['classname']};
$instance->{'get'.$parameters['method']}( $parameters );

.. o qualche sua variazione, a seconda della particolare implementazione.


1
Grazie per l'ennesimo approfondimento! Molto premuroso! Stavo pensando di avere anche il parametro language nell'URL. Questo sembra semplicemente il modo migliore per identificare una determinata lingua, non solo per l'utente ma per scopi SEO. Nel caso in cui un utente cambi / en / news in / de / news, la mia idea era quella di fare un reindirizzamento 301 (permanente) a / de / nachrichten per esempio. Solo per assicurarsi che ogni lingua abbia un solo URL univoco per pagina (sempre per scopi SEO)
Joshua - Pendo

È sempre più difficile selezionare la risposta migliore, al momento ci sono circa 3/4 risposte che meritano almeno una parte della generosità ciascuna. Combinati diventano una solida risposta a tutto ciò che volevo chiarire insieme :)
Joshua - Pendo,

Ho accettato la tua risposta per darti almeno qualche rappresentante extra per la risposta dettagliata che hai dato sulla traduzione dell'URL. Molto apprezzato! Tuttavia, la generosità è premiante per la persona sotto di te poiché ha risposto ad ogni aspetto della mia domanda in modo indipendente dalla piattaforma.
Giosuè - Pendo,

52

Implementazione di i18n senza prestazioni ridotte utilizzando un pre-processore come suggerito da Thomas Bley

Al lavoro, abbiamo recentemente implementato i18n su un paio di proprietà e una delle cose con cui abbiamo continuato a lottare è stato il successo delle prestazioni nel trattare con la traduzione al volo, poi ho scoperto questo fantastico post sul blog di Thomas Bley che ha ispirato il modo in cui stiamo usando i18n per gestire grandi carichi di traffico con problemi di prestazioni minime.

Invece di chiamare le funzioni per ogni operazione di traduzione, che come sappiamo in PHP è costosa, definiamo i nostri file di base con segnaposto, quindi utilizziamo un pre-processore per memorizzare nella cache quei file (memorizziamo il tempo di modifica dei file per assicurarci di servire gli ultimi contenuti in ogni momento).

I tag di traduzione

Thomas utilizza {tr}e {/tr}tag per definire dove iniziano e finiscono le traduzioni. A causa del fatto che stiamo usando TWIG, non vogliamo usare {per evitare confusione, quindi usiamo [%tr%]e [%/tr%]invece. Fondamentalmente, questo assomiglia a questo:

`return [%tr%]formatted_value[%/tr%];`

Nota che Thomas suggerisce di usare l'inglese di base nel file. Non lo facciamo perché non vogliamo modificare tutti i file di traduzione se cambiamo il valore in inglese.

I file INI

Quindi, creiamo un file INI per ogni lingua, nel formato placeholder = translated:

// lang/fr.ini
formatted_value = number_format($value * Model_Exchange::getEurRate(), 2, ',', ' ') . '€'

// lang/en_gb.ini
formatted_value = '£' . number_format($value * Model_Exchange::getStgRate())

// lang/en_us.ini
formatted_value = '$' . number_format($value)

Sarebbe banale per consentire a un utente di modificare questi all'interno del CMS, appena ottenere le coppie di chiavi da un preg_splitsu \no =e rendendo il CMS in grado di scrivere i file INI.

Il componente pre-processore

In sostanza, Thomas suggerisce di usare un 'compilatore' just-in-time (anche se, in verità, è un preprocessore) come questo per prendere i tuoi file di traduzione e creare file PHP statici su disco. In questo modo, essenzialmente memorizziamo nella cache i nostri file tradotti invece di chiamare una funzione di traduzione per ogni stringa nel file:

// This function was written by Thomas Bley, not by me
function translate($file) {
  $cache_file = 'cache/'.LANG.'_'.basename($file).'_'.filemtime($file).'.php';
  // (re)build translation?
  if (!file_exists($cache_file)) {
    $lang_file = 'lang/'.LANG.'.ini';
    $lang_file_php = 'cache/'.LANG.'_'.filemtime($lang_file).'.php';

    // convert .ini file into .php file
    if (!file_exists($lang_file_php)) {
      file_put_contents($lang_file_php, '<?php $strings='.
        var_export(parse_ini_file($lang_file), true).';', LOCK_EX);
    }
    // translate .php into localized .php file
    $tr = function($match) use (&$lang_file_php) {
      static $strings = null;
      if ($strings===null) require($lang_file_php);
      return isset($strings[ $match[1] ]) ? $strings[ $match[1] ] : $match[1];
    };
    // replace all {t}abc{/t} by tr()
    file_put_contents($cache_file, preg_replace_callback(
      '/\[%tr%\](.*?)\[%\/tr%\]/', $tr, file_get_contents($file)), LOCK_EX);
  }
  return $cache_file;
}

Nota: non ho verificato il funzionamento di regex, non l'ho copiato dal nostro server aziendale, ma puoi vedere come funziona l'operazione.

Come chiamarlo

Ancora una volta, questo esempio è di Thomas Bley, non di me:

// instead of
require("core/example.php");
echo (new example())->now();

// we write
define('LANG', 'en_us');
require(translate('core/example.php'));
echo (new example())->now();

Memorizziamo la lingua in un cookie (o variabile di sessione se non riusciamo a ottenere un cookie) e la recuperiamo ad ogni richiesta. Potresti combinarlo con un $_GETparametro opzionale per sovrascrivere la lingua, ma non suggerisco sottodominio per lingua o pagina per lingua perché renderà più difficile vedere quali pagine sono popolari e ridurrà il valore dell'ingresso link man mano che li avrai più diffusi.

Perché usare questo metodo?

Ci piace questo metodo di preelaborazione per tre motivi:

  1. L'enorme vantaggio in termini di prestazioni derivante dal non chiamare un sacco di funzioni per contenuti che cambiano raramente (con questo sistema, 100k visitatori in francese finiranno per eseguire la sostituzione della traduzione solo una volta).
  2. Non aggiunge alcun carico al nostro database, poiché utilizza semplici file flat ed è una soluzione puramente PHP.
  3. La capacità di usare espressioni PHP all'interno delle nostre traduzioni.

Ottenere contenuti di database tradotti

Aggiungiamo semplicemente una colonna per il contenuto del nostro database chiamato language, quindi usiamo un metodo di accesso per la LANGcostante che abbiamo definito in precedenza, quindi le nostre chiamate SQL (usando ZF1, purtroppo) sono simili a queste:

$query = select()->from($this->_name)
                 ->where('language = ?', User::getLang())
                 ->where('id       = ?', $articleId)
                 ->limit(1);

I nostri articoli hanno una chiave primaria composta ide languagequindi l'articolo 54può esistere in tutte le lingue. L' LANGimpostazione predefinita è en_USse non specificato.

Traduzione URL Slug

Combinerei due cose qui, una è una funzione nel tuo bootstrap che accetta un $_GETparametro per la lingua e sovrascrive la variabile cookie, e un'altra è il routing che accetta più lumache. Quindi puoi fare qualcosa del genere nel tuo percorso:

"/wilkommen" => "/welcome/lang/de"
... etc ...

Questi potrebbero essere memorizzati in un file semplice che può essere facilmente scritto dal tuo pannello di amministrazione. JSON o XML possono fornire una buona struttura per supportarli.

Note su alcune altre opzioni

Traduzione al volo basata su PHP

Non riesco a vedere che offrono alcun vantaggio rispetto alle traduzioni preelaborate.

Traduzioni basate sul front-end

Li ho trovati a lungo interessanti, ma ci sono alcuni avvertimenti. Ad esempio, devi rendere disponibile all'utente l'intero elenco di frasi sul tuo sito web che prevedi di tradurre, questo potrebbe essere problematico se ci sono aree del sito che stai nascondendo o alle quali non hai consentito l'accesso.

Dovresti anche presumere che tutti i tuoi utenti siano disposti e in grado di utilizzare Javascript sul tuo sito, ma dalle mie statistiche, circa il 2,5% dei nostri utenti sta funzionando senza di esso (o usando Noscript per impedire ai nostri siti di usarlo) .

Traduzioni basate su database

Le velocità di connettività del database di PHP non sono nulla di cui scrivere, e questo aumenta il già elevato costo di chiamare una funzione su ogni frase da tradurre. I problemi di prestazioni e scalabilità sembrano schiaccianti con questo approccio.


Vedo che ti ho confuso con "Traduzioni front-end", quello che intendevo era un modo per analizzare le stringhe tradotte sullo schermo. Non sono assolutamente alla ricerca di un modo per tradurlo sul lato client! Quello che intendevo era il modo più semplice per cambiare le lingue sul front-end, ma ovviamente è usando un cookie o un'impostazione utente :)
Joshua - Pendo,

Oh, e Database-Driven mi concentravo maggiormente sul metodo di gestione di tutte le traduzioni, quindi la mia soluzione ideale sarebbe un back-end che scriva traduzioni in un database seguito da una funzione che genera il componente di pre-elaborazione che genera il PHP file. Why?: semplice .. Voglio non essere disturbato da piccoli cambiamenti nel testo, gli utenti dovrebbero essere in grado di farlo da soli senza usare un editor di codice e / o un programma ftp :)
Joshua - Pendo

@PENDO So che non intendevi traduzioni front-end, è stato un commento leggermente velato verso l'utente che ha suggerito framework di traduzione front-end usando JS. ;)
Glitch Desire,

@PENDO Sono d'accordo, utilizzerei il back-end come mi hai suggerito, ma invece di un database utilizzerei un file flat per motivi di prestazioni. Naturalmente, il suggerimento principale qui è il pre-rendering dei modelli su di cambiamento così si potrebbe sostituire i .INIfile con una tabella di database a 3 colonne con placeholder, replacement, language. Tasto composto su placeholdere language. Quindi avere un altro 2-col con tempfile(percorso al modello) e modified(DATETIME).
Glitch Desire,

1
@PENDO Grazie. Ho effettuato il backup di 250 e ho intenzione di assegnarlo a teresko in 24 ore quando il sito me lo consente, poiché hai selezionato entrambe le risposte come corrette e penso che una divisione rappresenti al meglio le tue intenzioni.
Glitch Desire,

15

Ti suggerisco di non inventare una ruota e utilizzare gettext e l'elenco abbrevs delle lingue ISO. Hai visto come implementato i18n / l10n in CMS o framework popolari?

Usando gettext avrai un potente strumento in cui molti casi sono già implementati come forme plurali di numeri. In inglese hai solo 2 opzioni: singolare e plurale. Ma in russo per esempio ci sono 3 forme e non è così semplice come in inglese.

Inoltre molti traduttori hanno già esperienza di lavoro con gettext.

Dai un'occhiata a CakePHP o Drupal . Entrambi abilitati multilingue. CakePHP come esempio di localizzazione dell'interfaccia e Drupal come esempio di traduzione di contenuti.

Per l10n l'uso del database non è affatto il caso. Ci saranno tonnellate di domande. L'approccio standard prevede di ottenere tutti i dati di l10n in memoria nella fase iniziale (o durante la prima chiamata alla funzione i10n se si preferisce il caricamento lento). Può leggere dal file .po o dal DB tutti i dati contemporaneamente. E oltre a leggere le stringhe richieste dall'array.

Se è necessario implementare uno strumento online per tradurre l'interfaccia, è possibile avere tutti quei dati nel DB, ma comunque salvare tutti i dati su file per lavorare con esso. Per ridurre la quantità di dati in memoria è possibile dividere tutti i messaggi / stringhe tradotti in gruppi e caricare solo i gruppi necessari se sarà possibile.

Quindi hai perfettamente ragione nel tuo # 3. Con un'eccezione: di solito è un file di grandi dimensioni non un file per controller o giù di lì. Perché è meglio che le prestazioni aprano un file. Probabilmente sai che alcune app Web a carico elevato compila tutto il codice PHP in un file per evitare operazioni sui file quando includi / richiedi una chiamata.

Informazioni sugli URL. Google suggerisce indirettamente di utilizzare la traduzione:

per indicare chiaramente il contenuto francese: http://example.ca/fr/vélo-de-montagne.html

Inoltre penso che devi reindirizzare l'utente al prefisso della lingua predefinita, ad es. Http://examlpe.com/about-us reindirizzerà a http://examlpe.com/en/about-us Ma se il tuo sito utilizza solo una lingua, non ho bisogno di prefissi.

Dai un'occhiata a: http://www.audiomicro.com/trailer-hit-impact-psychodrama-sound-effects-836925 http://nl.audiomicro.com/aanhangwagen-hit-effect-psychodrama-geluidseffecten-836925 http: / /de.audiomicro.com/anhanger-hit-auswirkungen-psychodrama-sound-effekte-836925

La traduzione di contenuti è un'attività più difficile. Penso che ci saranno alcune differenze con diversi tipi di contenuti, ad esempio articoli, voci di menu, ecc. Ma in # 4 sei nel modo giusto. Dai un'occhiata a Drupal per avere più idee. Ha uno schema DB abbastanza chiaro e un'interfaccia abbastanza buona per la traduzione. Come se stessi creando un articolo e seleziona la lingua per esso. E più tardi puoi tradurlo in altre lingue.

Interfaccia di traduzione Drupal

Penso che non sia un problema con le lumache URL. Puoi semplicemente creare una tabella separata per le lumache e sarà la decisione giusta. Anche usando gli indici giusti non è un problema interrogare la tabella anche con una grande quantità di dati. E non si trattava della ricerca full-text ma della corrispondenza delle stringhe se userà il tipo di dati varchar per slug e puoi avere un indice anche su quel campo.

PS Mi dispiace, il mio inglese è tutt'altro che perfetto.


Grazie per il tempo impiegato per rispondere alla mia domanda. Il tuo inglese è abbastanza buono per me da capire! Ti farò già +1 per i tuoi sforzi!
Giosuè - Pendo,

Yaroslav, ancora una volta, grazie per la tua risposta. Comunque sono andato con altre 2 risposte che erano un po 'più complete e spiegavano i metodi usati dietro il codice invece di sottolineare che è già lì.
Giosuè - Pendo,

2
Nessun problema. In effetti questa risposta è più completa e interessante da leggere anche per me. Ma spero che tu abbia ottenuto qualcosa di utile anche dalla mia risposta.
Yaroslav,

12

Dipende da quanto contenuto ha il tuo sito web. All'inizio ho usato un database come tutte le altre persone qui, ma può essere dispendioso in termini di tempo scrivere tutto il funzionamento di un database. Non dico che questo è un metodo ideale e soprattutto se hai molto testo, ma se vuoi farlo velocemente senza usare un database, questo metodo potrebbe funzionare, tuttavia, non puoi consentire agli utenti di inserire dati che verrà utilizzato come file di traduzione. Ma se aggiungi le traduzioni da solo, funzionerà:

Diciamo che hai questo testo:

Welcome!

Puoi inserirlo in un database con traduzioni, ma puoi anche farlo:

$welcome = array(
"English"=>"Welcome!",
"German"=>"Willkommen!",
"French"=>"Bienvenue!",
"Turkish"=>"Hoşgeldiniz!",
"Russian"=>"Добро пожаловать!",
"Dutch"=>"Welkom!",
"Swedish"=>"Välkommen!",
"Basque"=>"Ongietorri!",
"Spanish"=>"Bienvenito!"
"Welsh"=>"Croeso!");

Ora, se il tuo sito Web utilizza un cookie, hai questo ad esempio:

$_COOKIE['language'];

Per semplificarlo, trasformiamolo in un codice che può essere facilmente utilizzato:

$language=$_COOKIE['language'];

Se la tua lingua dei cookie è gallese e hai questo codice:

echo $welcome[$language];

Il risultato di questo sarà:

Croeso!

Se è necessario aggiungere molte traduzioni per il tuo sito Web e un database richiede troppo, l'utilizzo di un array può essere la soluzione ideale.


1
Non è vicino alla risposta che stavo chiedendo. Inoltre, piuttosto che avere tutte le lingue disponibili in ogni pagina, è meglio creare file come quelli lang.en.phpda includere e utilizzare $lang['welcome']quali sono dichiarati in ciascun file.
Giosuè - Pendo,

7

Ti suggerirò di non dipendere davvero dal database per la traduzione, potrebbe essere davvero un compito complicato e potrebbe essere un problema estremo in caso di codifica dei dati.

Avevo affrontato un problema simile da tempo fa e ho scritto la seguente lezione per risolvere il mio problema

Oggetto: Locale \ Locale

<?php

  namespace Locale;

  class Locale{

// Following array stolen from Zend Framework
public $country_to_locale = array(
    'AD' => 'ca_AD',
    'AE' => 'ar_AE',
    'AF' => 'fa_AF',
    'AG' => 'en_AG',
    'AI' => 'en_AI',
    'AL' => 'sq_AL',
    'AM' => 'hy_AM',
    'AN' => 'pap_AN',
    'AO' => 'pt_AO',
    'AQ' => 'und_AQ',
    'AR' => 'es_AR',
    'AS' => 'sm_AS',
    'AT' => 'de_AT',
    'AU' => 'en_AU',
    'AW' => 'nl_AW',
    'AX' => 'sv_AX',
    'AZ' => 'az_Latn_AZ',
    'BA' => 'bs_BA',
    'BB' => 'en_BB',
    'BD' => 'bn_BD',
    'BE' => 'nl_BE',
    'BF' => 'mos_BF',
    'BG' => 'bg_BG',
    'BH' => 'ar_BH',
    'BI' => 'rn_BI',
    'BJ' => 'fr_BJ',
    'BL' => 'fr_BL',
    'BM' => 'en_BM',
    'BN' => 'ms_BN',
    'BO' => 'es_BO',
    'BR' => 'pt_BR',
    'BS' => 'en_BS',
    'BT' => 'dz_BT',
    'BV' => 'und_BV',
    'BW' => 'en_BW',
    'BY' => 'be_BY',
    'BZ' => 'en_BZ',
    'CA' => 'en_CA',
    'CC' => 'ms_CC',
    'CD' => 'sw_CD',
    'CF' => 'fr_CF',
    'CG' => 'fr_CG',
    'CH' => 'de_CH',
    'CI' => 'fr_CI',
    'CK' => 'en_CK',
    'CL' => 'es_CL',
    'CM' => 'fr_CM',
    'CN' => 'zh_Hans_CN',
    'CO' => 'es_CO',
    'CR' => 'es_CR',
    'CU' => 'es_CU',
    'CV' => 'kea_CV',
    'CX' => 'en_CX',
    'CY' => 'el_CY',
    'CZ' => 'cs_CZ',
    'DE' => 'de_DE',
    'DJ' => 'aa_DJ',
    'DK' => 'da_DK',
    'DM' => 'en_DM',
    'DO' => 'es_DO',
    'DZ' => 'ar_DZ',
    'EC' => 'es_EC',
    'EE' => 'et_EE',
    'EG' => 'ar_EG',
    'EH' => 'ar_EH',
    'ER' => 'ti_ER',
    'ES' => 'es_ES',
    'ET' => 'en_ET',
    'FI' => 'fi_FI',
    'FJ' => 'hi_FJ',
    'FK' => 'en_FK',
    'FM' => 'chk_FM',
    'FO' => 'fo_FO',
    'FR' => 'fr_FR',
    'GA' => 'fr_GA',
    'GB' => 'en_GB',
    'GD' => 'en_GD',
    'GE' => 'ka_GE',
    'GF' => 'fr_GF',
    'GG' => 'en_GG',
    'GH' => 'ak_GH',
    'GI' => 'en_GI',
    'GL' => 'iu_GL',
    'GM' => 'en_GM',
    'GN' => 'fr_GN',
    'GP' => 'fr_GP',
    'GQ' => 'fan_GQ',
    'GR' => 'el_GR',
    'GS' => 'und_GS',
    'GT' => 'es_GT',
    'GU' => 'en_GU',
    'GW' => 'pt_GW',
    'GY' => 'en_GY',
    'HK' => 'zh_Hant_HK',
    'HM' => 'und_HM',
    'HN' => 'es_HN',
    'HR' => 'hr_HR',
    'HT' => 'ht_HT',
    'HU' => 'hu_HU',
    'ID' => 'id_ID',
    'IE' => 'en_IE',
    'IL' => 'he_IL',
    'IM' => 'en_IM',
    'IN' => 'hi_IN',
    'IO' => 'und_IO',
    'IQ' => 'ar_IQ',
    'IR' => 'fa_IR',
    'IS' => 'is_IS',
    'IT' => 'it_IT',
    'JE' => 'en_JE',
    'JM' => 'en_JM',
    'JO' => 'ar_JO',
    'JP' => 'ja_JP',
    'KE' => 'en_KE',
    'KG' => 'ky_Cyrl_KG',
    'KH' => 'km_KH',
    'KI' => 'en_KI',
    'KM' => 'ar_KM',
    'KN' => 'en_KN',
    'KP' => 'ko_KP',
    'KR' => 'ko_KR',
    'KW' => 'ar_KW',
    'KY' => 'en_KY',
    'KZ' => 'ru_KZ',
    'LA' => 'lo_LA',
    'LB' => 'ar_LB',
    'LC' => 'en_LC',
    'LI' => 'de_LI',
    'LK' => 'si_LK',
    'LR' => 'en_LR',
    'LS' => 'st_LS',
    'LT' => 'lt_LT',
    'LU' => 'fr_LU',
    'LV' => 'lv_LV',
    'LY' => 'ar_LY',
    'MA' => 'ar_MA',
    'MC' => 'fr_MC',
    'MD' => 'ro_MD',
    'ME' => 'sr_Latn_ME',
    'MF' => 'fr_MF',
    'MG' => 'mg_MG',
    'MH' => 'mh_MH',
    'MK' => 'mk_MK',
    'ML' => 'bm_ML',
    'MM' => 'my_MM',
    'MN' => 'mn_Cyrl_MN',
    'MO' => 'zh_Hant_MO',
    'MP' => 'en_MP',
    'MQ' => 'fr_MQ',
    'MR' => 'ar_MR',
    'MS' => 'en_MS',
    'MT' => 'mt_MT',
    'MU' => 'mfe_MU',
    'MV' => 'dv_MV',
    'MW' => 'ny_MW',
    'MX' => 'es_MX',
    'MY' => 'ms_MY',
    'MZ' => 'pt_MZ',
    'NA' => 'kj_NA',
    'NC' => 'fr_NC',
    'NE' => 'ha_Latn_NE',
    'NF' => 'en_NF',
    'NG' => 'en_NG',
    'NI' => 'es_NI',
    'NL' => 'nl_NL',
    'NO' => 'nb_NO',
    'NP' => 'ne_NP',
    'NR' => 'en_NR',
    'NU' => 'niu_NU',
    'NZ' => 'en_NZ',
    'OM' => 'ar_OM',
    'PA' => 'es_PA',
    'PE' => 'es_PE',
    'PF' => 'fr_PF',
    'PG' => 'tpi_PG',
    'PH' => 'fil_PH',
    'PK' => 'ur_PK',
    'PL' => 'pl_PL',
    'PM' => 'fr_PM',
    'PN' => 'en_PN',
    'PR' => 'es_PR',
    'PS' => 'ar_PS',
    'PT' => 'pt_PT',
    'PW' => 'pau_PW',
    'PY' => 'gn_PY',
    'QA' => 'ar_QA',
    'RE' => 'fr_RE',
    'RO' => 'ro_RO',
    'RS' => 'sr_Cyrl_RS',
    'RU' => 'ru_RU',
    'RW' => 'rw_RW',
    'SA' => 'ar_SA',
    'SB' => 'en_SB',
    'SC' => 'crs_SC',
    'SD' => 'ar_SD',
    'SE' => 'sv_SE',
    'SG' => 'en_SG',
    'SH' => 'en_SH',
    'SI' => 'sl_SI',
    'SJ' => 'nb_SJ',
    'SK' => 'sk_SK',
    'SL' => 'kri_SL',
    'SM' => 'it_SM',
    'SN' => 'fr_SN',
    'SO' => 'sw_SO',
    'SR' => 'srn_SR',
    'ST' => 'pt_ST',
    'SV' => 'es_SV',
    'SY' => 'ar_SY',
    'SZ' => 'en_SZ',
    'TC' => 'en_TC',
    'TD' => 'fr_TD',
    'TF' => 'und_TF',
    'TG' => 'fr_TG',
    'TH' => 'th_TH',
    'TJ' => 'tg_Cyrl_TJ',
    'TK' => 'tkl_TK',
    'TL' => 'pt_TL',
    'TM' => 'tk_TM',
    'TN' => 'ar_TN',
    'TO' => 'to_TO',
    'TR' => 'tr_TR',
    'TT' => 'en_TT',
    'TV' => 'tvl_TV',
    'TW' => 'zh_Hant_TW',
    'TZ' => 'sw_TZ',
    'UA' => 'uk_UA',
    'UG' => 'sw_UG',
    'UM' => 'en_UM',
    'US' => 'en_US',
    'UY' => 'es_UY',
    'UZ' => 'uz_Cyrl_UZ',
    'VA' => 'it_VA',
    'VC' => 'en_VC',
    'VE' => 'es_VE',
    'VG' => 'en_VG',
    'VI' => 'en_VI',
    'VN' => 'vn_VN',
    'VU' => 'bi_VU',
    'WF' => 'wls_WF',
    'WS' => 'sm_WS',
    'YE' => 'ar_YE',
    'YT' => 'swb_YT',
    'ZA' => 'en_ZA',
    'ZM' => 'en_ZM',
    'ZW' => 'sn_ZW'
);

/**
 * Store the transaltion for specific languages
 *
 * @var array
 */
protected $translation = array();

/**
 * Current locale
 *
 * @var string
 */
protected $locale;

/**
 * Default locale
 *
 * @var string
 */
protected $default_locale;

/**
 *
 * @var string
 */
protected $locale_dir;

/**
 * Construct.
 *
 *
 * @param string $locale_dir            
 */
public function __construct($locale_dir)
{
    $this->locale_dir = $locale_dir;
}

/**
 * Set the user define localte
 *
 * @param string $locale            
 */
public function setLocale($locale = null)
{
    $this->locale = $locale;

    return $this;
}

/**
 * Get the user define locale
 *
 * @return string
 */
public function getLocale()
{
    return $this->locale;
}

/**
 * Get the Default locale
 *
 * @return string
 */
public function getDefaultLocale()
{
    return $this->default_locale;
}

/**
 * Set the default locale
 *
 * @param string $locale            
 */
public function setDefaultLocale($locale)
{
    $this->default_locale = $locale;

    return $this;
}

/**
 * Determine if transltion exist or translation key exist
 *
 * @param string $locale            
 * @param string $key            
 * @return boolean
 */
public function hasTranslation($locale, $key = null)
{
    if (null == $key && isset($this->translation[$locale])) {
        return true;
    } elseif (isset($this->translation[$locale][$key])) {
        return true;
    }

    return false;
}

/**
 * Get the transltion for required locale or transtion for key
 *
 * @param string $locale            
 * @param string $key            
 * @return array
 */
public function getTranslation($locale, $key = null)
{
    if (null == $key && $this->hasTranslation($locale)) {
        return $this->translation[$locale];
    } elseif ($this->hasTranslation($locale, $key)) {
        return $this->translation[$locale][$key];
    }

    return array();
}

/**
 * Set the transtion for required locale
 *
 * @param string $locale
 *            Language code
 * @param string $trans
 *            translations array
 */
public function setTranslation($locale, $trans = array())
{
    $this->translation[$locale] = $trans;
}

/**
 * Remove transltions for required locale
 *
 * @param string $locale            
 */
public function removeTranslation($locale = null)
{
    if (null === $locale) {
        unset($this->translation);
    } else {
        unset($this->translation[$locale]);
    }
}

/**
 * Initialize locale
 *
 * @param string $locale            
 */
public function init($locale = null, $default_locale = null)
{
    // check if previously set locale exist or not
    $this->init_locale();
    if ($this->locale != null) {
        return;
    }

    if ($locale == null || (! preg_match('#^[a-z]+_[a-zA-Z_]+$#', $locale) && ! preg_match('#^[a-z]+_[a-zA-Z]+_[a-zA-Z_]+$#', $locale))) {
        $this->detectLocale();
    } else {
        $this->locale = $locale;
    }

    $this->init_locale();
}

/**
 * Attempt to autodetect locale
 *
 * @return void
 */
private function detectLocale()
{
    $locale = false;

    // GeoIP
    if (function_exists('geoip_country_code_by_name') && isset($_SERVER['REMOTE_ADDR'])) {

        $country = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']);

        if ($country) {

            $locale = isset($this->country_to_locale[$country]) ? $this->country_to_locale[$country] : false;
        }
    }

    // Try detecting locale from browser headers
    if (! $locale) {

        if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {

            $languages = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);

            foreach ($languages as $lang) {

                $lang = str_replace('-', '_', trim($lang));

                if (strpos($lang, '_') === false) {

                    if (isset($this->country_to_locale[strtoupper($lang)])) {

                        $locale = $this->country_to_locale[strtoupper($lang)];
                    }
                } else {

                    $lang = explode('_', $lang);

                    if (count($lang) == 3) {
                        // language_Encoding_COUNTRY
                        $this->locale = strtolower($lang[0]) . ucfirst($lang[1]) . strtoupper($lang[2]);
                    } else {
                        // language_COUNTRY
                        $this->locale = strtolower($lang[0]) . strtoupper($lang[1]);
                    }

                    return;
                }
            }
        }
    }

    // Resort to default locale specified in config file
    if (! $locale) {
        $this->locale = $this->default_locale;
    }
}

/**
 * Check if config for selected locale exists
 *
 * @return void
 */
private function init_locale()
{
    if (! file_exists(sprintf('%s/%s.php', $this->locale_dir, $this->locale))) {
        $this->locale = $this->default_locale;
    }
}

/**
 * Load a Transtion into array
 *
 * @return void
 */
private function loadTranslation($locale = null, $force = false)
{
    if ($locale == null)
        $locale = $this->locale;

    if (! $this->hasTranslation($locale)) {
        $this->setTranslation($locale, include (sprintf('%s/%s.php', $this->locale_dir, $locale)));
    }
}

/**
 * Translate a key
 *
 * @param
 *            string Key to be translated
 * @param
 *            string optional arguments
 * @return string
 */
public function translate($key)
{
    $this->init();
    $this->loadTranslation($this->locale);

    if (! $this->hasTranslation($this->locale, $key)) {

        if ($this->locale !== $this->default_locale) {

            $this->loadTranslation($this->default_locale);

            if ($this->hasTranslation($this->default_locale, $key)) {

                $translation = $this->getTranslation($this->default_locale, $key);
            } else {
                // return key as it is or log error here
                return $key;
            }
        } else {
            return $key;
        }
    } else {
        $translation = $this->getTranslation($this->locale, $key);
    }
    // Replace arguments
    if (false !== strpos($translation, '{a:')) {
        $replace = array();
        $args = func_get_args();
        for ($i = 1, $max = count($args); $i < $max; $i ++) {
            $replace['{a:' . $i . '}'] = $args[$i];
        }
        // interpolate replacement values into the messsage then return
        return strtr($translation, $replace);
    }

    return $translation;
  }
}

uso

 <?php
    ## /locale/en.php

    return array(
       'name' => 'Hello {a:1}'
       'name_full' => 'Hello {a:1} {a:2}'
   );

$locale = new Locale(__DIR__ . '/locale');
$locale->setLocale('en');// load en.php from locale dir
//want to work with auto detection comment $locale->setLocale('en');

echo $locale->translate('name', 'Foo');
echo $locale->translate('name', 'Foo', 'Bar');

Come funziona

{a:1}viene sostituito dal 1 ° argomento passato al metodo Locale::translate('key_name','arg1') {a:2}viene sostituito dal 2 ° argomento passato al metodoLocale::translate('key_name','arg1','arg2')

Come funziona il rilevamento

  • Per impostazione predefinita, se geoipinstallato, restituirà il prefisso del paese geoip_country_code_by_namee se geoip non è installato il fallback HTTP_ACCEPT_LANGUAGEnell'intestazione

In che modo un database sarebbe disordinato? A causa dei possibili personaggi in diverse lingue? Finora ho principalmente siti Web in inglese, francese, olandese e tedesco, quindi per ora non è un problema. Grazie per la risposta, ma poiché è solo una parte della risposta, non vincerà la taglia.
Giosuè - Pendo,

beh, immagino che la tua domanda sia utile solo a te, ci sarebbero alcuni ragazzi che prenderebbero in considerazione l'uso di lingue come hindi, tailandese, cinese e arabo (queste lingue impiegheranno più di 1 byte per rappresentare un carattere) contro le tue lingue richieste. se si sta utilizzando db, le utf8_general_ciregole di confronto sono appropriate per farlo.
Shushant,

Sono d'accordo, ho avuto un po 'di traccia me stesso lì. Grazie per aver sottolineato, anche i personaggi multi-bit sono abbastanza importanti da essere menzionati in questa domanda :)
Joshua - Pendo

5

Solo una risposta secondaria: usa assolutamente gli URL tradotti con un identificatore di lingua davanti a loro: http://www.domain.com/nl/over-ons Le
soluzioni ibride tendono a complicarsi, quindi mi limiterei a seguirlo. Perché? Perché l'URL è essenziale per la SEO.

Informazioni sulla traduzione db: il numero di lingue è più o meno fisso? O piuttosto imprevedibile e dinamico? Se è risolto, aggiungerei semplicemente nuove colonne, altrimenti andrei con più tabelle.

Ma in generale, perché non usare Drupal? So che tutti vogliono costruire il proprio CMS perché è più veloce, più snello, ecc. Ecc. Ma questa è davvero una cattiva idea!


1
Grazie per la tua risposta. Il motivo per cui non voglio usare Drupal / Joomla è semplice: voglio assicurarmi di conoscere tutti i dettagli del mio sistema, i difetti plausibili, come viene costruito il codice (e importante: non compilare da 300 programmatori insieme) . Ho motivi più che sufficienti per non scegliere l'open source. Oltre a ciò, voglio che la mia azienda sia un fattore importante per i miei clienti, è una brutta cosa che possano andare da qualsiasi altro sviluppatore e lasciarmi indietro senza niente.
Giosuè - Pendo,

7
Penso che tutte queste ragioni siano contestate in tonnellate di articoli. Spero che i tuoi clienti non ti sceglieranno esattamente perché hai un CMS proprietario che nessun altro può mantenere. Ma comunque, questa è una discussione totalmente diversa.
Remy,

1
Capisco il tuo punto, preferisco ancora un sistema per il quale conosco tutti i dettagli e non provo nulla per fare affidamento sul lavoro di qualcun altro quando utilizzo un plugin.
Joshua - Pendo,

1
Inoltre, tendo a documentare il mio lavoro abbastanza bene, dal momento che sono un "esercito di un uomo", le persone che lavorano per me non dovrebbero avere difficoltà a conoscere il sistema.
Giosuè - Pendo,

La cattiva idea è quella di scegliere Drupal e persino Google dice che a loro non importa se l'URL viene tradotto o no. Tuttavia, deve contenere un identificatore locale.
undefinedman

5

Non tenterò di affinare le risposte già fornite. Invece ti parlerò del modo in cui il mio framework OOP PHP gestisce le traduzioni.

Internamente, il mio framework usa codici come en, fr, es, cn e così via. Un array contiene le lingue supportate dal sito Web: array ('en', 'fr', 'es', 'cn') Il codice della lingua viene passato tramite $ _GET (lang = fr) e se non passato o non valido, esso è impostato sulla prima lingua dell'array. Quindi, in qualsiasi momento durante l'esecuzione del programma e fin dall'inizio, la lingua corrente è nota.

È utile comprendere il tipo di contenuto che deve essere tradotto in un'applicazione tipica:

1) messaggi di errore da classi (o codice procedurale) 2) messaggi non di errore da classi (o codice procedurale) 3) contenuto della pagina (di solito memorizzato in un database) 4) stringhe a livello di sito (come il nome del sito Web) 5) script- stringhe specifiche

Il primo tipo è semplice da capire. Fondamentalmente, stiamo parlando di messaggi come "impossibile connettersi al database ...". Questi messaggi devono essere caricati solo quando si verifica un errore. La mia classe manager riceve una chiamata dalle altre classi e l'utilizzo delle informazioni passate come parametri va semplicemente alla cartella della classe pertinente e recupera il file di errore.

Il secondo tipo di messaggio di errore è più simile ai messaggi che ricevi quando la convalida di un modulo è andata storta. ("Non puoi lasciare ... vuoto" o "scegli una password con più di 5 caratteri"). Le stringhe devono essere caricate prima dell'esecuzione della classe. So cos'è

Per il contenuto effettivo della pagina, utilizzo una tabella per lingua, ciascuna tabella preceduta dal codice per la lingua. Quindi en_content è la tabella con contenuti in lingua inglese, es_content è per la spagna, cn_content per la Cina e fr_content è roba francese.

Il quarto tipo di stringa è rilevante in tutto il sito Web. Questo viene caricato tramite un file di configurazione chiamato usando il codice per la lingua, ovvero en_lang.php, es_lang.php e così via. Nel file di lingua globale dovrai caricare le lingue tradotte come array ("inglese", "cinese", "spagnolo", "francese") nel file globale inglese e array ("anglais", "chinois", " Espagnol ',' Francais ') nel dossier francese. Pertanto, quando si compila un menu a discesa per la selezione della lingua, è nella lingua corretta;)

Finalmente hai le stringhe specifiche dello script. Quindi, se scrivi un'applicazione di cottura, potrebbe essere "Il tuo forno non era abbastanza caldo".

Nel mio ciclo di applicazione, il file di lingua globale viene caricato per primo. Lì troverai non solo stringhe globali (come "Sito Web di Jack") ma anche impostazioni per alcune classi. Fondamentalmente tutto ciò che dipende dalla lingua o dalla cultura. Alcune delle stringhe presenti includono maschere per le date (MMDDYYYY o DDMMYYYY) o codici lingua ISO. Nel file di lingua principale, includo stringhe per le singole classi perché ce ne sono così poche.

Il secondo e ultimo file della lingua letto dal disco è il file della lingua dello script. lang_en_home_welcome.php è il file di lingua per lo script home / welcome. Uno script è definito da una modalità (home) e un'azione (benvenuto). Ogni script ha una propria cartella con file config e lang.

Lo script estrae il contenuto dal database denominando la tabella dei contenuti come spiegato sopra.

Se qualcosa va storto, il gestore sa dove ottenere il file di errore dipendente dalla lingua. Quel file viene caricato solo in caso di errore.

Quindi la conclusione è ovvia. Pensa ai problemi di traduzione prima di iniziare a sviluppare un'applicazione o un framework. È inoltre necessario un flusso di lavoro di sviluppo che incorpori le traduzioni. Con il mio framework, sviluppo l'intero sito in inglese e quindi traduco tutti i file pertinenti.

Solo una breve parola finale sul modo in cui vengono implementate le stringhe di traduzione. Il mio framework ha un unico globale, $ manager, che esegue i servizi disponibili per qualsiasi altro servizio. Quindi, ad esempio, il servizio moduli ottiene il servizio html e lo utilizza per scrivere l'html. Uno dei servizi sul mio sistema è il servizio di traduzione. $ traduttore-> set ($ servizio, $ codice, $ stringa) imposta una stringa per la lingua corrente. Il file della lingua è un elenco di tali dichiarazioni. $ traduttore-> get ($ servizio, $ codice) recupera una stringa di traduzione. Il codice $ può essere numerico come 1 o una stringa come 'no_connection'. Non ci può essere scontro tra servizi perché ognuno ha il proprio spazio dei nomi nell'area dati del traduttore.

Lo inserisco qui nella speranza che salverà a qualcuno il compito di reinventare la ruota come avrei dovuto fare qualche anno fa.


4

Ho avuto lo stesso problema un po 'di tempo fa, prima di iniziare a usare il framework Symfony .

  1. Basta usare una funzione __ () che ha gli arametri pageId (o objectId, objectTable descritti nel n. 2), la lingua di destinazione e un parametro opzionale della lingua di fallback (impostazione predefinita). La lingua predefinita potrebbe essere impostata in alcune configurazioni globali per avere un modo più semplice per cambiarla in un secondo momento.

  2. Per memorizzare il contenuto nel database ho usato la seguente struttura: (pageId, lingua, contenuto, variabili).

    • pageId sarebbe un FK per la tua pagina che vuoi tradurre. se hai altri oggetti, come notizie, gallerie o altro, basta dividerlo in 2 campi objectId, objectTable.

    • lingua - ovviamente memorizzerebbe la stringa di lingua ISO EN_en, LT_lt, EN_us ecc.

    • contenuto: il testo che si desidera tradurre insieme ai caratteri jolly per la sostituzione delle variabili. Esempio "Ciao mr. %% name %%. Il saldo del tuo account è %% balance %%."

    • variabili - le variabili codificate json. PHP fornisce funzioni per analizzarle rapidamente. Esempio "nome: Laurynas, saldo: 15,23".

    • hai citato anche campo di lumache. puoi aggiungerlo liberamente a questa tabella solo per avere un modo rapido per cercarlo.

  3. Le chiamate al database devono essere ridotte al minimo con la memorizzazione nella cache delle traduzioni. Deve essere memorizzato nell'array PHP, poiché è la struttura più veloce nel linguaggio PHP. Come farai questa memorizzazione nella cache dipende da te. Dalla mia esperienza dovresti avere una cartella per ogni lingua supportata e un array per ogni pageId. La cache deve essere ricostruita dopo aver aggiornato la traduzione. SOLO l'array modificato dovrebbe essere rigenerato.

  4. penso di aver risposto nel # 2

  5. la tua idea è perfettamente logica. questo è piuttosto semplice e penso che non ti farà alcun problema.

Gli URL devono essere tradotti utilizzando le lumache memorizzate nella tabella di traduzione.

Parole finali

è sempre bene ricercare le migliori pratiche, ma non reinventare la ruota. basta prendere e utilizzare i componenti da framework ben noti e usarli.

dai un'occhiata al componente di traduzione di Symfony . Potrebbe essere una buona base di codice per te.


Grazie per il commento, richiedi un +1 per il tempo impiegato. Laravel (nel mio caso) sta usando alcune parti di Symfony se non sbaglio, quindi hai assolutamente ragione a non reinventare la ruota. Ho iniziato questa domanda (e generosità) per ottenere alcune intuizioni sul modo in cui gli altri fanno le traduzioni, sto iniziando a credere che ci siano molte migliori pratiche là fuori :-)
Joshua - Pendo

1

Mi sono posto ripetutamente domande correlate, poi mi sono perso nei linguaggi formali ... ma solo per aiutarti un po 'vorrei condividere alcuni risultati:

Consiglio di dare un'occhiata al CMS avanzato

Typo3per PHP (so che ci sono molte cose, ma è quello che penso sia più maturo)

Plone nel Python

Se scopri che il Web nel 2013 dovrebbe funzionare in modo diverso, inizia da zero. Ciò significherebbe riunire un team di persone altamente qualificate / esperte per costruire un nuovo CMS. Forse ti piacerebbe dare un'occhiata al polimero per quello scopo.

Se si tratta di codifica e siti Web multilingue / supporto per la lingua madre, penso che ogni programmatore dovrebbe avere un indizio sull'unicode. Se non conosci unicode sicuramente confonderai i tuoi dati. Non andare con le migliaia di codici ISO. Ti risparmieranno solo un po 'di memoria. Ma puoi fare letteralmente tutto con UTF-8 anche archiviare caratteri cinesi. Ma per questo avresti bisogno di memorizzare caratteri a 2 o 4 byte che lo rendono sostanzialmente un utf-16 o utf-32.

Se si tratta di codifica URL, di nuovo lì non dovresti mescolare codifiche ed essere consapevole che almeno per il nome di dominio ci sono regole definite da diverse lobby che forniscono applicazioni come un browser. ad esempio un dominio potrebbe essere molto simile come:

ьankofamerica.com o bankofamerica.com samesame ma diverso;)

Naturalmente è necessario che il filesystem funzioni con tutte le codifiche. Un altro vantaggio per Unicode utilizzando il filesystem utf-8.

Se si tratta di traduzioni, pensa alla struttura dei documenti. ad esempio un libro o un articolo. Hai le docbookspecifiche per capire quelle strutture. Ma in HTML si tratta solo di blocchi di contenuti. Quindi ti piacerebbe avere una traduzione a quel livello, anche a livello di pagina web o dominio. Quindi se un blocco non esiste non è proprio lì, se non esiste una pagina web verrai reindirizzato al livello di navigazione superiore. Se un dominio dovrebbe essere completamente diverso nella struttura di navigazione, allora ... è una struttura completamente diversa da gestire. Questo può essere già fatto con Typo3.

Se si tratta di framework, i più maturi che conosco, per fare cose generali come MVC (parola d'ordine la odio davvero! Come "performance" Se vuoi vendere qualcosa, usa la parola performance e featurerich e vendi ... cosa l'inferno) è Zend. Ha dimostrato di essere una buona cosa per portare gli standard ai programmatori di caos php. Ma typo3 ha anche un Framework oltre al CMS. Recentemente è stato riqualificato e ora si chiama flow3. I framework ovviamente coprono l'astrazione del database, i modelli e i concetti per la memorizzazione nella cache, ma hanno punti di forza individuali.

Se si tratta di memorizzazione nella cache ... questo può essere incredibilmente complicato / multistrato. In PHP penserai ad accellerator, opcode, ma anche html, httpd, mysql, xml, css, js ... qualsiasi tipo di cache. Naturalmente alcune parti dovrebbero essere memorizzate nella cache e parti dinamiche come le risposte del blog non dovrebbero. Alcuni dovrebbero essere richiesti su AJAX con URL generati. JSON, hashbang ecc.

Quindi, ti piacerebbe avere qualsiasi piccolo componente sul tuo sito Web accessibile o gestito solo da determinati utenti , in modo concettualmente importante.

Inoltre ti piacerebbe fare statistiche , magari avere un sistema distribuito / Facebook di Facebook ecc. Qualsiasi software da costruire sopra il tuo CMS superiore ... quindi hai bisogno di diversi tipi di database in memoria, bigdata, xml, qualunque .

bene, penso che sia abbastanza per ora. Se non hai mai sentito parlare di typo3 / plone o dei framework menzionati, hai abbastanza da studiare. In questo percorso troverai molte soluzioni per le domande che non hai ancora posto.

Se poi pensi, facciamo un nuovo CMS perché il suo 2013 e php stanno per morire comunque, allora sei il benvenuto a far parte di qualsiasi altro gruppo di sviluppatori che speriamo non si perda.

In bocca al lupo!

E a proposito. che ne dici di persone che non avranno più siti Web in futuro? e saremo tutti su google +? Spero che gli sviluppatori diventino un po 'più creativi e facciano qualcosa di utile (per non essere assimilati dal borgle)

//// Modifica /// Solo un piccolo pensiero per l'applicazione esistente:

Se si dispone di un CMS mysql php e si desidera incorporare il supporto multilang. puoi utilizzare la tabella con una colonna adizionale per qualsiasi lingua o inserire la traduzione con un ID oggetto e un ID lingua nella stessa tabella o creare una tabella identica per qualsiasi lingua e inserire oggetti lì, quindi creare un'unione selezionata se lo desideri per averli tutti visualizzati. Per il database usa utf8 general ci e ovviamente in front / backend usa utf8 text / encoding. Ho usato segmenti di percorso URL per URL nel modo in cui hai già spiegato

domain.org/en/about puoi associare l'ID lang alla tabella dei contenuti. in ogni caso devi avere una mappa di parametri per i tuoi URL, quindi ti piacerebbe definire un parametro da mappare da un pathegment nel tuo URL che sarebbe ad es.

domain.org/en/about/employees/IT/administrators/

configurazione di ricerca

pageid | url

1 | /about/employees/../ ..

1 | /../about/employees../../

mappa parametri per url pathegment ""

$parameterlist[lang] = array(0=>"nl",1=>"en"); // default nl if 0
$parameterlist[branch] = array(1=>"IT",2=>"DESIGN"); // default nl if 0
$parameterlist[employertype] = array(1=>"admin",1=>"engineer"); //could be a sql result 

$websiteconfig[]=$userwhatever;
$websiteconfig[]=$parameterlist;
$someparameterlist[] = array("branch"=>$someid);
$someparameterlist[] = array("employertype"=>$someid);
function getURL($someparameterlist){ 
// todo foreach someparameter lookup pathsegment 
return path;
}

per dire, è già stato trattato nel post superiore.

E per non dimenticare, dovresti "riscrivere" l'URL nel tuo file php di generazione che nella maggior parte dei casi sarebbe index.php


Grazie per il commento, sicuramente ci sono cose a cui dovrei pensare. Sto usando la codifica utf8 già da un paio d'anni, una volta ho avuto difficoltà con il personaggio ;-) D'altra parte il tipo di CMS / Framework non doveva essere un fattore nella tua risposta mentre cercavo un metodo indipendente dalla piattaforma come se stessimo codificando da zero.
Giosuè - Pendo,

se vuoi davvero scrivere da zero, ti consiglio di dare un'occhiata a Dartlang e ai polimeri. Poiché dartlang funziona nel browser e ha supporto a 32 e 64 bit e può essere utilizzato per la maggior parte degli scopi sul lato server e ha un compilatore dart2js che vale davvero la pena studiare. Se le persone parlano di indipendenza dalla piattaforma, pensano a Java ... sappiamo cosa significa. Buildprocess ... Penso che userei JSON per lo scambio. generato dal sito Web client con hashbang e lato server .. fai tutto ciò che vuoi per garantire la collaborazione.
Dr. Dama,

La logica di databaselayout e di generazione è l'attività principale. Nessuno lo farà qui per te ... ma l'idea stessa è ciò che conta. Dal momento che non mi importa delle lobby ma per fare le cose, spero che tu possa creare modelli e condividere alcune cose. Sto lavorando su compiti simili in questo momento. Ma sono ancora in planata. Sto considerando Typo3 come un backend e creo una nuova struttura client. Il modello multilingue è risolto nel backend e condividerà le informazioni in modo dedicato per i motori di ricerca / servizi web. Ad ogni modo è tutto sensibile al contesto e un continuo compito di costruzione
Dr. Dama,

-1

Lavoro di database:

Crea 'Lingue' della tabella delle lingue:

campi:

language_id(primary and auto increamented)

language_name

created_at

created_by

updated_at

updated_by

Creare una tabella nel "contenuto" del database:

campi:

content_id(primary and auto incremented)

main_content

header_content

footer_content

leftsidebar_content

rightsidebar_content

language_id(foreign key: referenced to languages table)

created_at

created_by

updated_at

updated_by

Lavoro front-end:

Quando l'utente seleziona una lingua dall'elenco a discesa o un'area, quindi salva l'ID lingua selezionata nella sessione come,

$_SESSION['language']=1;

Ora recupera i dati dal 'contenuto' della tabella del database in base all'ID della lingua memorizzato nella sessione.

I dettagli possono essere trovati qui http://skillrow.com/multilingual-website-in-php-2/


1
Questo è un modo per una semplice integrazione del linguaggio allora necessaria, hai mai provato a leggere i post completi e hai dato le risposte?
Giosuè - Pendo,

-2

Come persona che vive in Quebec, dove quasi tutto il sito è francese e inglese ... ho provato molti, se non la maggior parte dei plugin multilingua per WP ... l'unica soluzione utile che funziona con tutto il mio sito è mQtranslate ... vivo e muoio con esso!

https://wordpress.org/plugins/mqtranslate/


1
si beh, WP non era un fattore della domanda. Questo potrebbe essere stato un commento
Joshua - Pendo,

-3

Che dire di WORDPRESS + MULTI-LANGUAGE SITE BASIS(plugin)? il sito avrà struttura:

  • example.com/ ita / categoria1 / ....
  • example.com/ ita / la mia pagina ....
  • esempio.com/ rus / categoria1 / ....
  • example.com/ rus / my-page ....

Il plugin fornisce l'interfaccia per la traduzione di tutte le frasi, con una logica semplice:

(ENG) my_title - "Hello user"
(SPA) my_title - "Holla usuario"

quindi può essere emesso:
echo translate('my_title', LNG); // LNG is auto-detected

ps comunque, controlla, se il plugin è ancora attivo.


3
e non è "Holla userio" in spagnolo è "Hola Usuario"
bheatcoker

1
Lol Holla userio, è stato divertente!
spekdrum,

per il motivo che non conoscevo lo spagnolo (esempio usato solo), sbrigati, affrettati a votare !! :)
T.Todua,

-5

Un'opzione davvero semplice che funziona con qualsiasi sito Web in cui è possibile caricare Javascript è www.multilingualizer.com

Ti permette di mettere tutto il testo per tutte le lingue su una pagina e quindi nasconde le lingue che l'utente non deve vedere. Funziona bene.


Attenzione, SEO sarebbe molto male! Inoltre, carichi tutto il contenuto mentre ne hai solo bisogno, una parte della quale è davvero una cattiva pratica.
Hafenkranich,

cose strane che il sito è solo in inglese ... perché non usano la loro soluzione ??
eduardo.lopes,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.