Guias
Estratégia de URL
Duas formas de levar o idioma: no endereço, ou em lado nenhum. Esta página é sobre escolher uma de propósito, porque essa escolha decide o teu SEO e mudá-la depois é caro.
As duas estratégias
| Estratégia | O que o visitante vê | Mudar de idioma é |
|---|---|---|
| No endereço | /docs em inglês, /es/docs em espanhol | Navegar para esse endereço |
| Fora do endereço | /docs em todos os idiomas | O texto a mudar onde está |
O Verbaly aceita as duas. Este site coloca o idioma no endereço em todas as páginas, porque à documentação se chega pela busca. Um app atrás de um login é o outro caso: troca de idioma com uma chamada e sem navegar, a partir de um único endereço. A estratégia é ditada pela superfície, não pelo seu gosto.
Dê um nome ao modo
Normalmente você não escreve nada. O Verbaly lê a sua montagem: se você constrói uma versão do seu site por idioma tem endereços por idioma, e se não, um único endereço serve para todos. Só diz em voz alta para discordar.
export default {
sourceLocale: 'en',
locales: ['en', 'es', 'pt'],
routing: 'prefix-all', // only when you disagree with what it read
};| Modo | O endereço | Quando |
|---|---|---|
prefix-except-source | /docs e /es/docs | Tudo o que as pessoas encontram buscando, que é quase qualquer site público |
prefix-all | /en/docs e /es/docs | Quando nenhum idioma é o da casa e todos são convidados |
no-prefix | /docs em todos os idiomas | Atrás de um login, onde ninguém chega de um resultado de busca |
Trocar de idioma é uma linha, e é a mesma linha em todos os modos. Importe-a de virtual:verbaly, onde ela já conhece os seus idiomas, a sua fonte e o seu modo.
import { switchLocale } from 'virtual:verbaly';
await switchLocale('es');Com o idioma no endereço ela vai para esse endereço, que já está escrito em espanhol, então não há espera nem piscada. Sem ele, o texto muda onde está e o endereço não se move. Nos dois casos ela lembra a escolha e ajusta o idioma da página. Dê a ela o router do seu framework e o seu aplicativo sobrevive à troca:
await switchLocale('es', { navigate: (path) => router.push(path) });npx verbaly doctor diz em que modo você está e se leu isso da sua configuração ou deduziu, para que um modo que ninguém escreveu não pareça uma decisão que alguém tomou. Também avisa quando a sua configuração pede duas coisas que não podem ser verdade ao mesmo tempo.
O cabeçalho é metade do que um resultado de busca mostra
Uma página pode estar traduzida em tudo o que um visitante olha e ainda assim dizer a um buscador que está em inglês, porque o título e a descrição moram no <head> e ninguém os lê na página. Essa é quase toda a razão de dar a um idioma o seu próprio endereço, então faça o bind deles como de todo o resto.
<title data-verbaly="page.title">URL strategy</title>
<meta name="description" content="…" data-verbaly-attr='{"content":"page.desc"}'>verbaly render preenche eles por idioma como o resto da página, e conta as páginas cujo título nunca muda para que você saiba uma vez e não por um resultado de busca. Nunca derruba um build: o nome de um produto pode não se traduzir.
A única diferença que não se contorna
Um motor de busca indexa endereços, não idiomas. Se o teu espanhol e o teu inglês vivem no mesmo endereço, só um é indexado, e não há truque de runtime que mude isso: as anotações hreflang existem precisamente para ligar endereços diferentes, por isso sem eles não há nada para ligar. A própria orientação da Google é usar URLs diferentes por idioma em vez de cookies ou definições do navegador.
O que o Verbaly te dá, conforme renderizas
Manter o idioma estável entre um clique e um F5 é um problema diferente conforme quem constrói o HTML. Estes são os três casos e o que cada um pede.
| Renderizas | No endereço | Fora do endereço |
|---|---|---|
| No navegador, uma SPA com router de cliente | localeFromPath lê o idioma do endereço em cada mudança de rota | resolveLocale + persistLocale. Um clique nunca recarrega, e um F5 volta a ler a escolha guardada |
| Num servidor, SvelteKit, Nuxt ou Next.js | O router do teu framework é dono do segmento, o Verbaly dos catálogos | resolveRequestLocale lê o cookie em cada pedido, por isso o documento chega já traduzido. Ligado por ti nas três integrações |
| No build, Astro, Eleventy ou qualquer SSG | verbaly render escreve o teu site em cada idioma, com hreflang e um sitemap | Nada faz isto bem. Serve-se um só documento a toda a gente, por isso o idioma só pode mudar depois de chegar |
Porque essa última célula está vazia em todo o lado, não só aqui
Um alojamento estático manda o mesmo ficheiro a todos. Para mostrar outro idioma sem mudar o endereço alguma coisa tem de correr antes de o HTML ser enviado, e num site estático não corre nada. As bibliotecas que oferecem este modo resolvem-no todas com um servidor a ler um cookie: o next-intl reescreve no seu middleware, e diz claramente que com o export estático do Next.js esse middleware não corre. O Verbaly não vai fingir o contrário. As tuas opções, pela ordem em que as tentaríamos:
- Pô-lo no endereço.
verbaly render, erender.redirectpara o visitante aterrar no seu idioma sem nunca ver como se decide. É o que este site faz. - Um subdomínio por idioma.
es.example.comnão leva segmento no caminho e mesmo assim tem endereço próprio, por isso a pesquisa continua a funcionar. Esta é a opção que toda a gente esquece. - Reescrever no edge. O teu alojamento pode servir o ficheiro pré-renderizado de
/es/no endereço simples conforme um cookie. Funciona, e custa-te: um só idioma indexado, e uma entrada de cache por valor de cookie no teu CDN. - Aceitar a troca. Serve o idioma de origem e deixa o
bindDomsubstituir o texto quando o catálogo chegar. Tudo bem por trás de um login, visível numa carga a frio.
Ficar com um só endereço, na prática
No navegador são duas chamadas. resolveLocale escolhe o idioma de quem chega sem um endereço de onde o ler, e persistLocale lembra a mudança para que o F5 seguinte concorde.
import { createVerbaly, persistLocale, resolveLocale } from 'verbaly';
const supported = ['en', 'es', 'pt'];
export const i18n = createVerbaly({
locale: resolveLocale({ supported, path: false }), // no language in the address
fallback: 'en',
loaders: { es: () => import('./locales/es.json') },
});
export async function switchTo(locale) {
await i18n.loadLocale(locale); // catalog first, so the text never flashes
i18n.setLocale(locale);
persistLocale(locale); // storage + <html lang> + <html dir>
}O que convém saber é path: false. Por omissão o resolveLocale lê o primeiro segmento do endereço antes de tudo, porque num site que põe lá o idioma o endereço é a única fonte que não pode contradizer a página. Desliga-o quando os teus endereços não forem idiomas, e uma rota chamada /es nunca poderá ser confundida com espanhol.
Num servidor a mesma ideia move-se um passo antes: o resolveRequestLocale lê o cookie, cai no Accept-Language e entrega o locale à instância que renderiza a resposta, por isso o visitante nunca recebe um documento no idioma errado. O @verbaly/sveltekit, o @verbaly/nuxt e o @verbaly/next já o fazem; o switchLocale é a metade do cliente e escreve o cookie que o servidor vai ler.
// the same decision, made before the html exists
const locale = resolveRequestLocale({ supported, cookie, header });Mudar de ideias mais tarde
Passar de um endereço para muitos é barato: ativas o render e cada idioma ganha o seu endereço mais o seu hreflang. Ao contrário custa-te os endereços que já publicaste, e os motores de busca guardam-nos muito tempo, por isso redireciona em vez de apagar. Nenhuma das direções toca nas tuas mensagens, nas tuas keys nem nos teus catálogos: isto é uma decisão de rotas, não de tradução.