Assegna a ogni lingua il proprio URL, poi annota quegli URL con tag hreflang reciproci. Un menu a tendina che sostituisce le stringhe nello stato React traduce l’interfaccia per chi è già sul sito, ma non crea nulla che un motore di ricerca possa indicizzare: le versioni tedesca e spagnola restano invisibili. globalize.now è un’infrastruttura di localizzazione basata su IA che estrae le stringhe hardcoded in cataloghi PO Lingui versionati e li mantiene sincronizzati a ogni push su Git. È questo che rende pratico mantenere route per locale, invece di trasformarlo in un lavoro da fare una volta sola.
È il passaggio che la maggior parte degli sviluppatori Lovable salta. L’app viene tradotta, lo switcher funziona nel browser e il traffico non arriva mai.
Perché Google non indicizza le altre lingue della mia app Lovable?
Perché quasi certamente quelle lingue non hanno un indirizzo.
Il pattern predefinito prodotto da un builder basato su IA è un valore locale useState e un menu a tendina che lo imposta. Ogni lingua viene renderizzata allo stesso URL. Un crawler che richiede quell’URL riceve una sola risposta, in una sola lingua, e indicizza una sola pagina. Non c’è nulla da scoprire e nessun segnale che indichi l’esistenza delle versioni nelle altre lingue.
La soluzione è strutturale, non cosmetica. Le indicazioni di Google sulle versioni localizzate sono chiare: le pagine nelle lingue alternative vengono identificate tramite URL e le annotazioni che le collegano devono essere restituite da ogni pagina dell’insieme. Se /de/pricing non esiste come indirizzo raggiungibile, nessun markup riuscirà a farla emergere.
Cosa deve fare un vero switcher di lingua?
Tre cose, in quest’ordine: cambiare l’URL, mantenere l’utente sulla stessa pagina e ricordare la scelta.
Cambiare l’URL è la parte che conta per la ricerca. Mantenere l’utente sulla stessa pagina è ciò che conta per l’esperienza, ed è il punto in cui la maggior parte delle implementazioni fallisce: passando da /en/pricing si deve arrivare a /de/pricing, non a /de/. Ricordare la scelta è una comodità e non deve mai sovrascrivere un locale esplicito nell’URL.
C’è un anti-pattern che vale la pena menzionare: i redirect automatici basati sulla lingua del browser. Se un crawler negli Stati Uniti richiede /de/pricing e viene reindirizzato a /en/pricing, la pagina tedesca di fatto non esiste. Offri la possibilità di cambiare, non imporla.
Come aggiungo il routing per locale a un’app Lovable con TanStack Start?
Aggiungi un segmento locale all’inizio dell’albero delle route e attiva da lì il catalogo.
A maggio 2026 Lovable ha spostato i nuovi progetti sul rendering lato server con TanStack Start, quindi una route con prefisso locale restituisce HTML completamente tradotto già nella prima risposta. È esattamente ciò che vuoi far ricevere a un crawler.
Crea un segmento dinamico che avvolga il resto delle route:
src/routes/
$locale/
route.tsx
index.tsx
pricing.tsx
Poi attiva il catalogo Lingui per quel locale prima del rendering delle route figlie:
// src/routes/$locale/route.tsx
import { createFileRoute, Outlet, notFound } from "@tanstack/react-router";
import { i18n } from "@lingui/core";
import { I18nProvider } from "@lingui/react";
const LOCALES = ["en", "de", "es", "fr"] as const;
export const Route = createFileRoute("/$locale")({
loader: async ({ params }) => {
if (!LOCALES.includes(params.locale as (typeof LOCALES)[number])) {
throw notFound();
}
const { messages } = await import(
`../../locales/${params.locale}/messages.po`
);
i18n.loadAndActivate({ locale: params.locale, messages });
return { locale: params.locale };
},
component: LocaleLayout,
});
function LocaleLayout() {
return (
<I18nProvider i18n={i18n}>
<Outlet />
</I18nProvider>
);
}
Qui fanno davvero la differenza due dettagli. Rifiutare i locali sconosciuti con notFound() impedisce a /xx/pricing di restituire un soft 200 per ogni percorso casuale provato da un crawler. Caricare il catalogo nel route loader fa sì che l’attivazione avvenga sul server, prima della serializzazione dell’HTML.
Se usi lo stack precedente, la forma dell’URL resta la stessa, ma cambia il percorso di rendering. Il tutorial sull’i18n di Lovable con Vite copre questo caso, mentre la guida alla configurazione di TanStack Start approfondisce il lato SSR.
Come creo il componente dello switcher di lingua?
Vai alla stessa route con un parametro locale diverso e renderizza ogni opzione come un link reale.
// src/components/LanguageSwitcher.tsx
import { Link, useLocation, useParams } from "@tanstack/react-router";
const LOCALES = {
en: "English",
de: "Deutsch",
es: "Español",
fr: "Français",
} as const;
export function LanguageSwitcher() {
const { locale } = useParams({ from: "/$locale" });
const { pathname } = useLocation();
const rest = pathname.replace(`/${locale}`, "") || "/";
return (
<nav aria-label="Language">
<ul>
{Object.entries(LOCALES).map(([code, label]) => (
<li key={code}>
<Link
to={`/${code}${rest}`}
hrefLang={code}
aria-current={code === locale ? "true" : undefined}
>
{label}
</Link>
</li>
))}
</ul>
</nav>
);
}
Renderizza le opzioni come anchor, non come button collegati a una chiamata del router. Gli anchor sono indicizzabili e offrono ai motori di ricerca un secondo percorso per scoprire le route locali, oltre alla sitemap. Etichetta ogni opzione nella sua lingua invece di usare una bandiera: le bandiere rappresentano paesi, e lo spagnolo non è un paese.
Come genero correttamente i tag hreflang?
Genera un’annotazione per ogni locale su ogni pagina dell’insieme, incluso un riferimento a sé stessa, più un x-default.
Google verifica che gli URL alternativi rimandino indietro e ignora le annotazioni non reciproche o che puntano lontano dal canonical della pagina. Per questo generarle programmaticamente è l’unico approccio sensato: quattro lingue su venti pagine significano 320 annotazioni da mantenere coerenti a mano.
In TanStack Start, creale nel head della route:
// src/routes/$locale/route.tsx (excerpt)
const SITE = "https://example.com";
const LOCALES = ["en", "de", "es", "fr"] as const;
export const Route = createFileRoute("/$locale")({
head: ({ params, location }) => {
const rest = location.pathname.replace(`/${params.locale}`, "") || "/";
return {
links: [
{ rel: "canonical", href: `${SITE}/${params.locale}${rest}` },
...LOCALES.map((code) => ({
rel: "alternate",
hrefLang: code,
href: `${SITE}/${code}${rest}`,
})),
{ rel: "alternate", hrefLang: "x-default", href: `${SITE}/en${rest}` },
],
};
},
});
Poiché le annotazioni derivano dallo stesso array LOCALES usato dal router, aggiungere una lingua aggiorna contemporaneamente route, switcher e insieme hreflang. È questa la proprietà da proteggere: un elenco, tre consumer.
Aggiungi gli stessi URL alla sitemap con gli alternates xhtml:link e assicurati che l’attributo <html lang> corrisponda al locale attivo. L’attributo lang non influisce su hreflang, ma influisce sugli screen reader e sui prompt di traduzione del browser.
Perché un widget di traduzione runtime non può farlo?
Perché traduce dopo che la pagina è già stata servita in inglese.
Un widget runtime sostituisce il testo nel browser quando il suo script viene caricato. La prima risposta ricevuta da un crawler contiene il DOM in inglese e la versione tradotta esiste solo dopo l’esecuzione del JavaScript lato client. Alcuni widget offrono modalità con sottodomini o sottodirectory e hreflang generato, ma in quel caso la struttura degli URL e le annotazioni appartengono all’infrastruttura del vendor, non alla tua repo.
Questa dipendenza ha un costo che non è più ipotetico. Lovalingo, un widget di traduzione popolare tra chi crea app con Lovable, chiuderà il 31 agosto 2026 e le sue stesse indicazioni invitano a passare a un’internazionalizzazione gestita dal progetto. Quando un widget scompare, scompaiono anche gli URL delle lingue che aveva generato e il sito torna in inglese. Cataloghi versionati e route definite nel tuo codice non hanno questo interruttore. Abbiamo descritto la versione pratica della migrazione nel confronto delle alternative a Lovalingo.
E per il classico stack Vite SPA?
La struttura degli URL è identica; il rendering no.
I progetti Lovable creati prima del passaggio a TanStack Start sono app single-page con Vite e React, servite come file statici e renderizzate nel browser. Puoi comunque fare routing su /:locale, generare hreflang e mantenere i cataloghi nella repo. Quello che perdi è la garanzia che il primo byte ricevuto da un crawler contenga contenuto tradotto.
Se la SEO è il motivo per cui stai localizzando, aggiungi il prerendering per le route locali, così ognuna restituisce HTML reale, oppure pianifica il passaggio allo stack con rendering lato server. Non risolvere aggiungendo un widget runtime sopra un’app renderizzata lato client: sovrappone due passaggi di rendering lato client e peggiora il first meaningful paint in ogni lingua.
Come impedisci a quattro locali di divergere?
Automatizza l’estrazione e tratta i cataloghi come codice sorgente.
Il motivo per cui il routing per locale viene abbandonato non è il routing. È che ogni nuova feature aggiunge stringhe inglesi che nessuno estrae, così /de/ si riempie lentamente di frammenti in inglese e finisce per sembrare peggio di non avere affatto il tedesco. globalize.now gestisce questo layer: aggiungi la skill lovable-i18n al workspace Lovable e chiedi a Lovable di configurare l’i18n, oppure installa le skill in locale:
npx skills add globalize-now/globalize-skills
Aggiungi --all per installarlo per ogni agent. Le nuove stringhe vengono estratte, le chiavi generate, i cataloghi tradotti e i file PO aggiornati vengono sottoposti a commit a ogni push su Git.
Il prezzo è di 20 € al mese per workspace e include 20 € di credito per la traduzione — circa 200.000 parole — ogni mese; oltre questa soglia, l’utilizzo è basato sui token con la stessa tariffa per carattere. Non ci sono costi per seat né per lingua, quindi aggiungere un quinto locale modifica la configurazione del routing, non la fattura. La registrazione include 5 € di credito per la traduzione e non richiede una carta. Maggiori dettagli per gli sviluppatori e per chi rilascia app create con l’IA.
Routing e hreflang si configurano in un pomeriggio. Mantenere completi quattro cataloghi mentre l’app cambia è la parte che non finisce mai, ed è quella che globalize.now automatizza.
globalize.now trasforma il testo hardcoded dell'app in file di localizzazione pronti e li mantiene aggiornati mentre rilasci.
Prova globalize.now gratis