Internazionalizzare un'applicazione PHP non significa soltanto tradurre pulsanti e messaggi. Un'applicazione predisposta per più lingue o mercati deve separare lingua, formati regionali, fuso orario, valuta, contenuto e policy di business. Se questi livelli vengono mescolati, ogni espansione può trasformarsi in una biforcazione funzionale difficile da testare e mantenere.
Cosa cambia operando in più lingue e mercati

La lingua determina come viene espresso un testo. Le impostazioni regionali, o locale, definiscono convenzioni di presentazione quali il separatore decimale, l'ordine delle date o il raggruppamento delle migliaia. Un locale può includere una regione, come es-ES o fr-CA, ma quella regione non deve sostituire il mercato, l'entità legale, la residenza, la policy commerciale né il Paese operativo.
È inoltre opportuno distinguere contesti che spesso viaggiano insieme, ma non significano la stessa cosa:
- Fuso orario: interpreta orari, scadenze, agende e chiusure operative.
- Valuta: identifica l'importo di una transazione o di un listino prezzi; non si deduce dalla lingua.
- Organizzazione o entità legale: può determinare imposte, autorizzazioni, fatturazione o conservazione dei dati.
- Mercato: può condizionare catalogo, logistica, metodi di pagamento o canali disponibili.
- Preferenze utente: lingua, locale e fuso orario scelti, che possono differire dalla configurazione aziendale.
Una persona può usare l'interfaccia in inglese, lavorare in un fuso orario europeo e gestire un'organizzazione che fattura in un'altra valuta. Ridurre questa realtà a un'unica variabile locale crea decisioni implicite.
L'errore costoso: trasformare la lingua in una regola di business
Un segnale negativo compare quando il codice prende decisioni di dominio a partire dalla lingua dell'interfaccia: if ($locale === 'es'). Questa condizione può iniziare mostrando un'etichetta diversa e finire per applicare imposte, nascondere un metodo di pagamento o modificare un'approvazione.
La domanda corretta è: “quale dato o policy spiega questa variazione?”. Se dipende da un'entità legale, va consultata tale entità. Se risponde a una policy commerciale, deve esistere una policy identificabile e versionabile. Se riguarda soltanto la rappresentazione, appartiene al confine di input o output.
La lingua traduce l'esperienza; non autorizza, calcola né definisce di per sé il comportamento del dominio.
Cosa deve rimanere nel dominio
Il dominio deve lavorare con concetti stabili e valori canonici. Un ordine necessita di quantità, importi, righe, stati e regole di calcolo; non deve sapere se un importo verrà mostrato come 1,234.50 o 1.234,50. Una policy di idoneità deve ricevere attributi espliciti, non leggere la presentazione dell'utente.
- Dominio: invarianti, stati, calcoli, autorizzazioni di business, policy ed eventi.
- Applicazione: casi d'uso, caricamento del contesto, coordinamento e selezione delle policy.
- Adapter di input: moduli, header, API o file; validazione e normalizzazione.
- Adapter di output: traduzione, serializzazione e formattazione di date, importi e unità.
In PHP, evitate che le entità e i servizi centrali consultino direttamente sessione, header HTTP, variabili d'ambiente o il locale globale del processo. Queste dipendenze fanno sì che lo stesso caso d'uso si comporti in modo diverso a seconda del canale o del momento di esecuzione.
Progettare un contesto esplicito e limitato
Un ExecutionContext può includere l'identificatore dell'organizzazione, l'attore, il locale di presentazione e il fuso orario preferito. Ogni campo deve avere una semantica chiara. La valuta di un'operazione, tuttavia, deve fare parte dell'importo o della policy di prezzo applicata, non di una preferenza globale mutabile.
Risolvete il contesto al confine di ogni canale. Una richiesta web può usare una preferenza salvata o una negoziazione controllata; un'API deve ricevere campi espliciti e documentati; un processo in coda deve persistere gli identificatori richiesti al momento della creazione. Un job asincrono non deve presumere che erediterà sessione, utente né fuso orario.
Contenuto traducibile e dati operativi
I testi dell'interfaccia, i template di comunicazione e il contenuto editoriale hanno un ciclo di vita diverso dai dati operativi. Usate chiavi stabili e semantiche, ad esempio billing.invoice.overdue, al posto del testo originale. In questo modo è possibile modificare la formulazione senza rompere codice, test o integrazioni.
Il contenuto gestibile richiede pubblicazione: una traduzione può esistere in bozza, essere approvata o pubblicata. Definite il fallback: lingua richiesta, lingua base dell'organizzazione e, se necessario, un'assenza visibile e controllata. Una traduzione alternativa può essere accettabile per una nota interna, ma non necessariamente per una comunicazione contrattuale.
Non duplicate un record operativo completo per lingua, salvo che il dato sia localizzato. Un prodotto può avere nome e descrizione traducibili, mentre identificatore, peso, stato e regole di disponibilità rimangono comuni. Se esiste una reale differenza commerciale, modellatela come variante o policy, non come traduzione.
Date, importi, unità e arrotondamenti
Memorizzate gli istanti temporali in modo non ambiguo e conservate il fuso orario quando il significato è locale. “La riunione inizia alle 09:00” richiede di conoscere il fuso in cui è stata definita; “l'evento si è verificato a quest'ora” richiede un istante assoluto. I cambi stagionali producono ore inesistenti o ripetute, quindi l'input deve essere validato e la risoluzione adottata deve essere registrata quando opportuno.
Per il denaro, memorizzate una quantità intera in unità minori insieme al codice valuta, ma non presumete due decimali. La scala o l'esponente deriva dai metadati della valuta applicabili all'operazione. Alcune valute usano una scala diversa da due, e i requisiti storici o di una rete di pagamento possono richiedere di conservare la scala effettiva o una versione della regola utilizzata.
final class Money {
public function __construct(
public readonly int $minorUnits,
public readonly string $currency,
public readonly int $scale
) {}
}
La scala consente di interpretare correttamente le unità minori, ma non sostituisce una policy di arrotondamento. Definite il punto di arrotondamento, il metodo applicato e la regola per il contante quando esiste, poiché l'arrotondamento alla cassa può differire dall'arrotondamento contabile. Evitate float, formati localizzati durante il calcolo e conversioni implicite.
Lo stesso criterio vale per le misure: conservate l'unità originale quando ha significato operativo, normalizzate quando il calcolo lo richiede e convertite soltanto durante l'acquisizione o la presentazione. Un modulo deve indicare l'unità e il formato consentiti; non deve indovinare se 1,500 significhi uno e mezzo o millecinquecento.
Architettura dei flussi di input e output
La separazione dei livelli deve emergere in un flusso completo e ripetibile:
- Acquisire contesto e dato di input: ottenere organizzazione, attore, canale, locale, fuso orario e valore ricevuto.
- Validare il formato consentito: verificare campi obbligatori, sintassi, unità, valuta, fuso orario e vincoli del canale.
- Normalizzare in valori canonici: convertire testo localizzato in importi, date, unità e identificatori non ambigui.
- Eseguire il caso d'uso di dominio: applicare regole e policy esplicite su valori canonici.
- Tradurre e formattare l'output: scegliere messaggi pubblicati e rappresentare valori per il destinatario o il contratto API.
Un'API può decidere di accettare soltanto formati canonici, come date con fuso esplicito e importi strutturati. Un modulo per utenti può accettare formati localizzati, purché il suo parser sia esplicito. In entrambi i casi, il dominio riceve la stessa rappresentazione stabile.
Policy configurabile o regola di business distinta
Una variazione è di solito configurabile se condivide il processo e modifica parametri dichiarabili, come un limite, un elenco di giorni festivi o un metodo di calcolo definito dalla configurazione. Questa configurazione necessita di schema, versione, responsabile e test.
La differenza rivela una regola distinta quando modifica invarianti, stati, responsabilità, fonti dati o conseguenze legali. In tal caso, nasconderla nelle opzioni crea una configurazione opaca. Modellate una policy tramite un'interfaccia esplicita, oppure un flusso separato se il processo è realmente diverso. L'obiettivo non è forzare un'unica astrazione, ma evitare di duplicare l'intera applicazione per una differenza localizzata.
Test e checklist

I test devono verificare calcolo e rappresentazione. Cambiare lingua o locale non deve alterare un totale, un'autorizzazione o una policy commerciale, salvo che un requisito esplicito lo stabilisca.
- Testate le date nei cambi d'orario, con ore ambigue e inesistenti.
- Coprite importi zero, negativi, scale diverse, arrotondamento contabile e per contante.
- Verificate input localizzati consentiti e rifiuto dei formati ambigui.
- Verificate fallback, assenza di contenuto pubblicato e variabili interpolate.
- Eseguite job asincroni senza sessione, usando soltanto il contesto persistito.
- Testate i contratti API con valori canonici e metadati di formato quando necessari.
Prima di aprire una lingua o un mercato, identificate cosa cambia realmente, separate le rispettive fonti di verità, riesaminate le regole monetarie e temporali, e attivate la disponibilità gradualmente quando l'operatività richiede una validazione controllata. Questa disciplina consente di ampliare l'applicazione senza trasformare ogni mercato in una versione parallela del prodotto.



