Portfolio Moretto uses react-i18next to provide a fully bilingual experience in English and Spanish. Every piece of user-facing text, from navigation labels to section content, is translatable through a centralized translation system.
Translation files are stored as JSON objects with dot-notation keys:
src/components/dictionaries/en.json
{ "header.home": "Home", "header.about": "About", "header.technology": "Skills", "header.works": "Experience", "header.projects": "Projects", "header.contact": "Contact", "header.openMenu": "Open menu", "header.closeMenu": "Close menu", "hero.tagline": "Software Developer", "hero.title": "I turn ideas into software that truly works", "hero.description": "I design, build, and maintain digital products that blend simple interfaces with solid, long-lasting architecture.", "hero.secondary": "I'm currently part of Asince SRL, where I focus on keeping critical systems stable while building new features that help the company grow.", "hero.ctaContact": "Let's collaborate", "hero.ctaProjects": "View projects", "hero.highlights": [ { "label": "Years in tech", "value": "2+" }, { "label": "Projects delivered", "value": "5+" }, { "label": "Based in", "value": "Argentina" } ], "footer.develop": "Developed by Federico Moretto", "footer.rights": "All rights reserved", "changeLanguage": "ES / EN"}
src/components/dictionaries/es.json
{ "header.home": "Inicio", "header.about": "Sobre mí", "header.technology": "Skills", "header.works": "Experiencia", "header.projects": "Proyectos", "header.contact": "Contacto", "header.openMenu": "Abrir menú", "header.closeMenu": "Cerrar menú", "hero.tagline": "Software Developer", "hero.title": "Transformo ideas en software que realmente funciona", "hero.description": "Diseño, desarrollo y mantengo productos digitales que combinan interfaces simples con una arquitectura sólida y pensada para durar.", "hero.secondary": "Hoy formo parte de Asince SRL, donde me enfoco en mantener estables los sistemas críticos y en crear nuevas funcionalidades que ayuden a que la empresa siga creciendo.", "hero.ctaContact": "Colaboremos", "hero.ctaProjects": "Ver proyectos", "hero.highlights": [ { "label": "Años en tecnología", "value": "2+" }, { "label": "Proyectos entregados", "value": "5+" }, { "label": "Desde", "value": "Argentina" } ], "footer.develop": "Desarrollado por Federico Moretto", "footer.rights": "Todos los derechos reservados", "changeLanguage": "EN / ES"}
Both translation files must have identical keys to ensure all content has translations in both languages. Missing keys will display the key name instead of translated text.
The current implementation does not persist language preference. The language resets to Spanish (default) on page refresh. To persist the choice, you would need to store the preference in localStorage and read it during initialization.
Example implementation (not in current codebase):
src/i18n.js (enhanced)
import i18n from 'i18next';import { initReactI18next } from 'react-i18next';import translationES from './components/dictionaries/es.json'import translationEN from './components/dictionaries/en.json'const resources = { es: { translation: translationES }, en: { translation: translationEN }}// Retrieve saved language or default to 'es'const savedLanguage = localStorage.getItem('language') || 'es'i18n .use(initReactI18next) .init({ resources, lng: savedLanguage, // Use saved preference debug: true, keySeparator: false, interpolation: { escapeValue: false, } })// Save language changes to localStoragei18n.on('languageChanged', (lng) => { localStorage.setItem('language', lng)})export default i18n;
"header.home": "Home","header.about": "About","header.contact": "Contact","footer.develop": "Developed by Federico Moretto","footer.rights": "All rights reserved"
This makes translation files easier to navigate and maintain.
Handle Missing Translations Gracefully
Always validate array data before rendering:
const highlights = t("hero.highlights", { returnObjects: true })// Check if it's actually an array{Array.isArray(highlights) && highlights.map(...)}
Use Optional Chaining for Nested Objects
When accessing nested properties, use optional chaining to prevent errors:
Create src/components/dictionaries/fr.json with all the same keys as en.json and es.json:
src/components/dictionaries/fr.json
{ "header.home": "Accueil", "header.about": "À propos", "hero.title": "Je transforme les idées en logiciels qui fonctionnent vraiment", // ... all other keys}