Add a language switcher with English, Italian, Spanish and French

The interface is translated through @nuxtjs/i18n: strings live in
i18n/locales/*.json, routes are prefixed except for the default English, and
useLocaleHead emits the lang attribute, the hreflang alternates and og:locale.

The switcher is a native select rather than a custom dropdown: keyboard
support, the platform picker on mobile and correct labelling come for free.

Article content stays single-language — this translates the site chrome only.
This commit is contained in:
2026-08-25 14:23:09 +02:00
parent f5639111f9
commit c08f53ca69
21 changed files with 3197 additions and 87 deletions
+50
View File
@@ -0,0 +1,50 @@
{
"site": {
"tagline": "Articles, guides and analysis.",
"skipToContent": "Skip to content",
"mainNavigation": "Main navigation",
"languageLabel": "Language",
"allArticles": "All articles",
"home": "Home",
"browse": "Browse",
"categories": "Categories",
"contact": "Contact",
"noCategories": "Nothing published yet",
"rights": "All rights reserved."
},
"home": {
"latest": "Latest",
"readArticle": "Read the article",
"moreStories": "More stories",
"viewAll": "View all articles"
},
"list": {
"archive": "The archive",
"articles": "Articles",
"articlesPage": "Articles — page {page}",
"archiveDescription": "Every article published on the blog.",
"category": "Category",
"categoryDescription": "Every article in the {name} category.",
"loading": "Loading…",
"empty": "Nothing has been published yet.",
"categoryEmpty": "There are no articles in this category.",
"failed": "Articles could not be loaded. Please try again later."
},
"pagination": {
"label": "Pagination",
"previous": "Previous page",
"next": "Next page",
"position": "Page {page} of {total}"
},
"article": {
"breadcrumb": "Breadcrumb",
"back": "Back to all articles"
},
"error": {
"notFoundTitle": "Page not found",
"notFoundBody": "The page you are looking for does not exist or has moved.",
"genericTitle": "Something went wrong",
"genericBody": "Please try again in a moment.",
"backHome": "Back to home"
}
}
+50
View File
@@ -0,0 +1,50 @@
{
"site": {
"tagline": "Artículos, guías y análisis.",
"skipToContent": "Ir al contenido",
"mainNavigation": "Navegación principal",
"languageLabel": "Idioma",
"allArticles": "Todos los artículos",
"home": "Inicio",
"browse": "Explorar",
"categories": "Categorías",
"contact": "Contacto",
"noCategories": "Aún no hay publicaciones",
"rights": "Todos los derechos reservados."
},
"home": {
"latest": "Último",
"readArticle": "Leer el artículo",
"moreStories": "Más artículos",
"viewAll": "Ver todos los artículos"
},
"list": {
"archive": "El archivo",
"articles": "Artículos",
"articlesPage": "Artículos — página {page}",
"archiveDescription": "Todos los artículos publicados en el blog.",
"category": "Categoría",
"categoryDescription": "Todos los artículos de la categoría {name}.",
"loading": "Cargando…",
"empty": "Todavía no se ha publicado nada.",
"categoryEmpty": "No hay artículos en esta categoría.",
"failed": "No se han podido cargar los artículos. Inténtalo de nuevo más tarde."
},
"pagination": {
"label": "Paginación",
"previous": "Página anterior",
"next": "Página siguiente",
"position": "Página {page} de {total}"
},
"article": {
"breadcrumb": "Ruta de navegación",
"back": "Volver a todos los artículos"
},
"error": {
"notFoundTitle": "Página no encontrada",
"notFoundBody": "La página que buscas no existe o se ha movido.",
"genericTitle": "Algo ha salido mal",
"genericBody": "Inténtalo de nuevo en unos instantes.",
"backHome": "Volver al inicio"
}
}
+50
View File
@@ -0,0 +1,50 @@
{
"site": {
"tagline": "Articles, guides et analyses.",
"skipToContent": "Aller au contenu",
"mainNavigation": "Navigation principale",
"languageLabel": "Langue",
"allArticles": "Tous les articles",
"home": "Accueil",
"browse": "Parcourir",
"categories": "Catégories",
"contact": "Contact",
"noCategories": "Rien n'a encore été publié",
"rights": "Tous droits réservés."
},
"home": {
"latest": "Dernier",
"readArticle": "Lire l'article",
"moreStories": "Autres articles",
"viewAll": "Voir tous les articles"
},
"list": {
"archive": "Les archives",
"articles": "Articles",
"articlesPage": "Articles — page {page}",
"archiveDescription": "Tous les articles publiés sur le blog.",
"category": "Catégorie",
"categoryDescription": "Tous les articles de la catégorie {name}.",
"loading": "Chargement…",
"empty": "Rien n'a encore été publié.",
"categoryEmpty": "Il n'y a aucun article dans cette catégorie.",
"failed": "Les articles n'ont pas pu être chargés. Veuillez réessayer plus tard."
},
"pagination": {
"label": "Pagination",
"previous": "Page précédente",
"next": "Page suivante",
"position": "Page {page} sur {total}"
},
"article": {
"breadcrumb": "Fil d'Ariane",
"back": "Retour à tous les articles"
},
"error": {
"notFoundTitle": "Page introuvable",
"notFoundBody": "La page que vous cherchez n'existe pas ou a été déplacée.",
"genericTitle": "Une erreur est survenue",
"genericBody": "Veuillez réessayer dans un instant.",
"backHome": "Retour à l'accueil"
}
}
+50
View File
@@ -0,0 +1,50 @@
{
"site": {
"tagline": "Articoli, guide e analisi.",
"skipToContent": "Vai al contenuto",
"mainNavigation": "Navigazione principale",
"languageLabel": "Lingua",
"allArticles": "Tutti gli articoli",
"home": "Home",
"browse": "Naviga",
"categories": "Categorie",
"contact": "Contatti",
"noCategories": "Ancora nessuna pubblicazione",
"rights": "Tutti i diritti riservati."
},
"home": {
"latest": "Ultimo",
"readArticle": "Leggi l'articolo",
"moreStories": "Altri articoli",
"viewAll": "Vedi tutti gli articoli"
},
"list": {
"archive": "L'archivio",
"articles": "Articoli",
"articlesPage": "Articoli — pagina {page}",
"archiveDescription": "Tutti gli articoli pubblicati sul blog.",
"category": "Categoria",
"categoryDescription": "Tutti gli articoli nella categoria {name}.",
"loading": "Caricamento…",
"empty": "Non è stato ancora pubblicato nulla.",
"categoryEmpty": "Non ci sono articoli in questa categoria.",
"failed": "Non è stato possibile caricare gli articoli. Riprova più tardi."
},
"pagination": {
"label": "Paginazione",
"previous": "Pagina precedente",
"next": "Pagina successiva",
"position": "Pagina {page} di {total}"
},
"article": {
"breadcrumb": "Percorso di navigazione",
"back": "Torna a tutti gli articoli"
},
"error": {
"notFoundTitle": "Pagina non trovata",
"notFoundBody": "La pagina che cerchi non esiste o è stata spostata.",
"genericTitle": "Qualcosa è andato storto",
"genericBody": "Riprova tra qualche istante.",
"backHome": "Torna alla home"
}
}