From ecb73e1684b42265af3f8d93541600e4d0f9c414 Mon Sep 17 00:00:00 2001
From: Ariel Costas Guerrero <ariel@costas.dev>
Date: Tue, 24 Jun 2025 16:14:28 +0200
Subject: Implement i18n

Closes #18
---
 src/frontend/app/routes/settings.tsx | 69 +++++++++++++++++++++---------------
 1 file changed, 40 insertions(+), 29 deletions(-)

(limited to 'src/frontend/app/routes/settings.tsx')

diff --git a/src/frontend/app/routes/settings.tsx b/src/frontend/app/routes/settings.tsx
index b5e91f1..e657c03 100644
--- a/src/frontend/app/routes/settings.tsx
+++ b/src/frontend/app/routes/settings.tsx
@@ -1,64 +1,75 @@
 import { useApp } from "../AppContext";
 import "./settings.css";
+import { useTranslation } from "react-i18next";
 
 export default function Settings() {
+    const { t, i18n } = useTranslation();
     const { theme, setTheme, tableStyle, setTableStyle, mapPositionMode, setMapPositionMode } = useApp();
 
     return (
         <div className="page-container">
-            <h1 className="page-title">Sobre UrbanoVigo Web</h1>
+            <h1 className="page-title">{t('about.title')}</h1>
             <p className="about-description">
-                Aplicación web para encontrar paradas y tiempos de llegada de los autobuses
-                urbanos de Vigo, España.
+                {t('about.description')}
             </p>
             <section className="settings-section">
-                <h2>Ajustes</h2>
+                <h2>{t('about.settings')}</h2>
                 <div className="settings-content-inline">
-                    <label htmlFor="theme" className="form-label-inline">Modo:</label>
-                    <select id="theme" className="form-select-inline" value={theme} onChange={(e) => setTheme(e.target.value as "light" | "dark")}>
-                        <option value="light">Claro</option>
-                        <option value="dark">Oscuro</option>
+                    <label htmlFor="theme" className="form-label-inline">{t('about.theme')}</label>
+                    <select id="theme" className="form-select-inline" value={theme} onChange={(e) => setTheme(e.target.value as "light" | "dark")}> 
+                        <option value="light">{t('about.theme_light')}</option>
+                        <option value="dark">{t('about.theme_dark')}</option>
                     </select>
                 </div>
                 <div className="settings-content-inline">
-                    <label htmlFor="tableStyle" className="form-label-inline">Estilo de tabla:</label>
-                    <select id="tableStyle" className="form-select-inline" value={tableStyle} onChange={(e) => setTableStyle(e.target.value as "regular" | "grouped")}>
-                        <option value="regular">Mostrar por orden</option>
-                        <option value="grouped">Agrupar por línea</option>
+                    <label htmlFor="tableStyle" className="form-label-inline">{t('about.table_style')}</label>
+                    <select id="tableStyle" className="form-select-inline" value={tableStyle} onChange={(e) => setTableStyle(e.target.value as "regular" | "grouped")}> 
+                        <option value="regular">{t('about.table_style_regular')}</option>
+                        <option value="grouped">{t('about.table_style_grouped')}</option>
                     </select>
                 </div>
                 <div className="settings-content-inline">
-                    <label htmlFor="mapPositionMode" className="form-label-inline">Posición del mapa:</label>
-                    <select id="mapPositionMode" className="form-select-inline" value={mapPositionMode} onChange={e => setMapPositionMode(e.target.value as 'gps' | 'last')}>
-                        <option value="gps">Posición GPS</option>
-                        <option value="last">Donde lo dejé</option>
+                    <label htmlFor="mapPositionMode" className="form-label-inline">{t('about.map_position_mode')}</label>
+                    <select id="mapPositionMode" className="form-select-inline" value={mapPositionMode} onChange={e => setMapPositionMode(e.target.value as 'gps' | 'last')}> 
+                        <option value="gps">{t('about.map_position_gps')}</option>
+                        <option value="last">{t('about.map_position_last')}</option>
+                    </select>
+                </div>
+                <div className="settings-content-inline">
+                    <label htmlFor="language" className="form-label-inline">Idioma:</label>
+                    <select
+                        id="language"
+                        className="form-select-inline"
+                        value={i18n.language}
+                        onChange={e => i18n.changeLanguage(e.target.value)}
+                    >
+                        <option value="es-ES">Español</option>
+                        <option value="gl-ES">Galego</option>
+                        <option value="en-GB">English</option>
                     </select>
                 </div>
                 <details className="form-details">
-                    <summary>¿Qué significa esto?</summary>
-                    <p>
-                        La tabla de horarios puede mostrarse de dos formas:
-                    </p>
+                    <summary>{t('about.details_summary')}</summary>
+                    <p>{t('about.details_table')}</p>
                     <dl>
-                        <dt>Mostrar por orden</dt>
-                        <dd>Las paradas se muestran en el orden en que se visitan. Aplicaciones como Infobus (Vitrasa) usan este estilo.</dd>
-                        <dt>Agrupar por línea</dt>
-                        <dd>Las paradas se agrupan por la línea de autobús. Aplicaciones como iTranvias (A Coruña) o Moovit (más o menos) usan este estilo.</dd>
+                        <dt>{t('about.table_style_regular')}</dt>
+                        <dd>{t('about.details_regular')}</dd>
+                        <dt>{t('about.table_style_grouped')}</dt>
+                        <dd>{t('about.details_grouped')}</dd>
                     </dl>
                 </details>
             </section>
-            <h2>Créditos</h2>
+            <h2>{t('about.credits')}</h2>
             <p>
                 <a href="https://github.com/arielcostas/urbanovigo-web" className="about-link" rel="nofollow noreferrer noopener">
-                    Código en GitHub
+                    {t('about.github')}
                 </a> -
-                Desarrollado por <a href="https://www.costas.dev" className="about-link" rel="nofollow noreferrer noopener">
+                {t('about.developed_by')} <a href="https://www.costas.dev" className="about-link" rel="nofollow noreferrer noopener">
                     Ariel Costas
                 </a>
             </p>
             <p>
-                Datos obtenidos de <a href="https://datos.vigo.org" className="about-link" rel="nofollow noreferrer noopener">datos.vigo.org</a> bajo
-                licencia <a href="https://opendefinition.org/licenses/odc-by/" className="about-link" rel="nofollow noreferrer noopener">Open Data Commons Attribution License</a>
+                {t('about.data_source_prefix')} <a href="https://datos.vigo.org" className="about-link" rel="nofollow noreferrer noopener">datos.vigo.org</a> {t('about.data_source_middle')} <a href="https://opendefinition.org/licenses/odc-by/" className="about-link" rel="nofollow noreferrer noopener">Open Data Commons Attribution License</a>
             </p>
         </div>
     )
-- 
cgit v1.3

