Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implemtacion de i18n, enrutamiento para ingles y español y botton para cambiar de idioma #19

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import { defineConfig } from 'astro/config';

import tailwind from "@astrojs/tailwind";
import { defineConfig } from 'astro/config';

// https://astro.build/config
export default defineConfig({
integrations: [tailwind()],
base: '/es/pagina-principal',
base: '/home',
vite: {
define: {
'process.env.ANGULAR_APP_URL': JSON.stringify(process.env.ANGULAR_APP_URL),
'process.env.GITHUB_TOKEN': JSON.stringify(process.env.GITHUB_TOKEN)
'process.env.GITHUB_TOKEN': JSON.stringify(process.env.GITHUB_TOKEN),
},
},
i18n: {
defaultLocale: 'en',
locales: ['en', 'es'],

routing: {
prefixDefaultLocale: true,
},
},
});
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@octokit/rest": "^21.0.1",
"astro": "^4.12.1",
"dotenv": "^16.4.5",
"i18n-js": "^4.4.3",
"octokit": "^4.0.2",
"tailwindcss": "^3.4.6",
"tailwindcss-animated": "^1.1.2",
Expand Down
45 changes: 24 additions & 21 deletions src/common/constants/general.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
// Assets paths
export const ASSETS_PATH = '/es/pagina-principal/assets';
export const IMAGES_PATH = `${ASSETS_PATH}/images/`;
export const ASSETS_PATH = '/home/assets/';
export const IMAGES_PATH = `${ASSETS_PATH}images/`;
export const VIDEOS_PATH = `https://res.cloudinary.com/dtsiwfy5p/video/upload`;

//App paths
export const HOME_PATH = '/es/pagina-principal';
export const SERVICES_PATH = '#services';
export const SAMPLE_PATH = '#sample';
export const WHO_WE_ARE_PATH = '#who-we-are';
export const COMMUNITY_PATH = '#community';

export const TERMS_AND_CONDITIONS_PATH = '/es/pagina-principal/terms-and-conditions';
export const PRIVACY_POLICY_PATH = '/es/pagina-principal/privacy-policy';

// API paths
export const ANGULAR_APP_URL = import.meta.env.ANGULAR_APP_URL || 'http://localhost:4200/es/entrevistas';
// TODO: Update Social URL
// Social paths
export const GITHUB_URL = 'https://github.com/EntrevistadorInteligente';
export const GITHUB_TOKEN = import.meta.env.GITHUB_TOKEN;
export const TWITCH_URL = 'https://www.twitch.tv/jamiltonqo';
export const LINKEDIN_URL = 'https://www.linkedin.com/in/jamilton-quintero-osorio/';
export const DISCORD_URL = 'https://discord.gg/2DUazgGCKr';
// Function to get paths based on language
export const getPaths = (lang) => {
return {
//App path
HOME_PATH: `/home/${lang}`,
SERVICES_PATH: '#services',
SAMPLE_PATH: '#sample',
WHO_WE_ARE_PATH: '#who-we-are',
COMMUNITY_PATH: '#community',
TERMS_AND_CONDITIONS_PATH: `/home/${lang}/terms-and-conditions`,
PRIVACY_POLICY_PATH: `/home/${lang}/privacy-policy`,
// API paths
ANGULAR_APP_URL: import.meta.env.ANGULAR_APP_URL || 'http://localhost:4200/entrevistas',
// TODO: Update Social URL
// Social paths
GITHUB_URL: 'https://github.com/EntrevistadorInteligente',
GITHUB_TOKEN: import.meta.env.GITHUB_TOKEN,
TWITCH_URL: 'https://www.twitch.tv/jamiltonqo',
LINKEDIN_URL: 'https://www.linkedin.com/in/jamilton-quintero-osorio/',
DISCORD_URL: 'https://discord.gg/2DUazgGCKr'
};
};
1 change: 1 addition & 0 deletions src/components/Button/Button.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const button = cva(
false: false,
},
size: {
ssm: 'h-9 px-7 py-1 text-base font-semibold',
sm: 'h-9 px-10 py-1 text-base font-semibold',
md: 'h-10 px-10 py-2.5 text-base font-semibold',
lg: 'h-11 px-10 py-2 text-lg font-semibold',
Expand Down
14 changes: 9 additions & 5 deletions src/components/Footers/MainFooter/MainFooter.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
---
import { cn, DISCORD_URL, IMAGES_PATH, LINKEDIN_URL, PRIVACY_POLICY_PATH, TERMS_AND_CONDITIONS_PATH, TWITCH_URL } from '@common';
import { cn, getPaths, IMAGES_PATH, } from '@common';
import { Icon } from '@components';
import { getLangFromUrl, useTranslations } from '@i18n/utils';
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
const { DISCORD_URL, TWITCH_URL, LINKEDIN_URL, PRIVACY_POLICY_PATH, TERMS_AND_CONDITIONS_PATH } = getPaths(lang);

interface Props {
/**
Expand Down Expand Up @@ -29,7 +33,7 @@ const classes = {
class="max-h-14 max-w-14"
/>
<div>
<p id="titulo-de-footer">Redes sociales</p>
<p id="titulo-de-footer">{t('footer.social-media')}</p>
<div class="mt-2 flex items-center gap-4">
<a href={DISCORD_URL} class="text-black transition-colors hover:text-primary-600" aria-label="Únete a nuestro servidor de Discord (se abre en una nueva pestaña)" target="_blank">
<Icon icon="discord" class="h-5 w-5 transition-transform hover:scale-110" iconStyle="solid" />
Expand All @@ -49,13 +53,13 @@ const classes = {
<!-- Legal Links Section -->
<div class="grid gap-4 text-center max-md:mt-10 md:text-right">
<a href={TERMS_AND_CONDITIONS_PATH} class="text-sm font-bold text-neutral-800 transition-colors hover:text-neutral-600"
>Términos de servicio</a
>{t('footer.terms-and-conditions')}</a
>
<a href={PRIVACY_POLICY_PATH} class="text-sm font-bold text-neutral-800 transition-colors hover:text-neutral-600"
>Políticas de privacidad</a
>{t('footer.privacy-policy')}</a
>
</div>
</div>
<p class="mt-7 text-center">Copyright {currentYear} © Entrevistador Inteligente</p>
<p class="mt-7 text-center">Copyright {currentYear} © {t('footer.title')}</p>
</div>
</footer>
18 changes: 12 additions & 6 deletions src/components/Navigations/RootNavbar/RootNavbar.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
---
import { ANGULAR_APP_URL, COMMUNITY_PATH, HOME_PATH, IMAGES_PATH, SAMPLE_PATH, SERVICES_PATH, WHO_WE_ARE_PATH } from '@common';
import { getPaths, IMAGES_PATH, } from '@common';
import { Button, Icon } from '@components';
import LanguagePicker from 'components/language-selector/languagePicker.astro';
import { getLangFromUrl, useTranslations } from '@i18n/utils';
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
const { HOME_PATH, COMMUNITY_PATH, SAMPLE_PATH, SERVICES_PATH, WHO_WE_ARE_PATH } = getPaths(lang);
---

<header class="mt-6">
Expand All @@ -16,11 +21,12 @@ import { Button, Icon } from '@components';
</a>
<nav id="navigation" class="lg:flex lg:items-center lg:gap-12" data-state="inactive">
<ul class="flex items-center gap-9 text-[#2D323E]">
<li><a class="transition-colors hover:text-neutral-400" href={HOME_PATH}>Inicio</a></li>
<li><a class="transition-colors hover:text-neutral-400" href={SERVICES_PATH}>Servicios</a></li>
<li><a class="transition-colors hover:text-neutral-400" href={SAMPLE_PATH}>Muestra</a></li>
<li><a class="transition-colors hover:text-neutral-400" href={WHO_WE_ARE_PATH}>¿Quienes somos?</a></li>
<li><a class="transition-colors hover:text-neutral-400" href={COMMUNITY_PATH}>Comunidad</a></li>
<li><a class="transition-colors hover:text-neutral-400" href={HOME_PATH}>{t('nav.home')}</a></li>
<li><a class="transition-colors hover:text-neutral-400" href={SERVICES_PATH}>{t('nav.services')}</a></li>
<li><a class="transition-colors hover:text-neutral-400" href={SAMPLE_PATH}>{t('nav.sample')}</a></li>
<li><a class="transition-colors hover:text-neutral-400" href={WHO_WE_ARE_PATH}>{t('nav.about')}</a></li>
<li><a class="transition-colors hover:text-neutral-400" href={COMMUNITY_PATH}>{t('nav.community')}</a></li>
<li><LanguagePicker /></li>
</ul>
<!-- TODO: Se comenta por el momento los botones CTA del header -->
<!-- <div class="button-container | flex items-center gap-4 max-lg:mt-10 max-lg:flex-wrap max-lg:justify-center max-lg:px-8">
Expand Down
64 changes: 64 additions & 0 deletions src/components/language-selector/languagePicker.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
import { languages } from '@i18n/ui';
import { Button }from '@components';
import { getLangFromUrl, useTranslations } from '@i18n/utils';
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
---

<div class="dropdown">
<Button isInvert size= { 'ssm' } class="flex gap-1">{t('lenguage-selector')}</Button>
<div class="dropdown-content">
{
Object.entries(languages).map(([lang, label]) => (
<a
href={`/home/${lang}/`}
onClick={() => lang}
>
{label}
</a>
))
}
</div>
</div>

<style>
/* Agrega estilos para la apariencia del botón y del desplegable */


.dropdown {
position: relative;
display: inline-block;
padding: auto;
}

.dropdown-content {
display: none;
position: absolute;
background-color: #f9f9f9;
min-width: 12px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 1;
width: 100%;
font-size: 1rem;
}

.dropdown-content a {
color: black;
padding: 10px 50px;
text-decoration: none;
display: flex;
justify-content: center;

}

.dropdown-content a:hover {
background-color: hsla(245, 59%, 59%, 0.8);
color: hsl(0, 0%, 100%);
}

.dropdown:hover .dropdown-content {
display: block;
}

</style>
126 changes: 126 additions & 0 deletions src/i18n/ui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
export const languages = {
en: 'English',
es: 'Español',
};

export const defaultLang = 'es';

export const ui = {
en: {
'nav.home': 'Home',
'nav.services': 'Services',
'nav.sample': 'Sample',
'nav.about': 'Who we are?',
'nav.community': 'Community',
'hero.cvslogan': 'Analyze your CV, evaluate job offers, and get immediate feedback',
'hero.register': 'Register',
'hero.motivational-quote': 'Go ahead, we believe in you, you can do it!',
'hero.demo': 'Demo',
'services.title': 'Services',
'services.our-services': 'Our Services',
'services.card.title': 'CV Analysis',
'services.card.description':
'We analyze your CV using Artificial Intelligence (AI) to determine your level of work experience and skills.',
'services.card.video-alt':
"Screenshot of a prototype test interface. On the left, there is a menu with several test options, including 'Find our Services page', 'What was your first impression...', 'What would you expect to have...', 'Was the Services page where...', 'Organize these pages into cat...', and 'Which of the following best d...'. On the right, the selected test is shown with details such as 'Task', 'Description', 'Prototype', and 'Expected path'.",
'services.card.title-2': 'Offers',
'services.card.description-2':
'Here you will fill out a form with the job description to prepare for your interview, giving you confidence and security.',
'services.card.video-alt-2':
"Screenshot of a prototype test interface with a yellow gradient background. On the left, there is a menu with several test options, including 'Find our Services page', 'What was your first impression...', 'What would you expect to have...', 'Was the Services page where...', 'Organize these pages into cat...', and 'Which of the following best d...'. On the right, the selected test is shown with details such as 'Task', 'Description', 'Prototype', and 'Expected path'.",
'services.card.title-3': 'Interview',
'services.card.description-3':
'To ensure the interview, we have made sure that key information, such as skills and experience, matches in both documents.',
'services.card.video-alt-3':
"Screenshot of a prototype test interface with a green gradient background. On the left, there is a menu with several test options, including 'Find our Services page', 'What was your first impression...', 'What would you expect to have...', 'Was the Services page where...', 'Organize these pages into cat...', and 'Which of the following best d...'. On the right, the selected test is shown with details such as 'Task', 'Description', 'Prototype', and 'Expected path'.",
'services.card.title-4': 'Feedback',
'services.card.description-4':
'Once the interview is completed, we will provide feedback on the interview, and you will know your strengths and areas for improvement.',
'services.card.video-alt-4':
"Screenshot of a prototype test interface with a blue gradient background. On the left, there is a menu with several test options, including 'Find our Services page', 'What was your first impression...', 'What would you expect to have...', 'Was the Services page where...', 'Organize these pages into cat...', and 'Which of the following best d...'. On the right, the selected test is shown with details such as 'Task', 'Description', 'Prototype', and 'Expected path'.",
'feature.title-2': 'Sample',
'feature.title': 'A sample of interviews',
'feature.button': 'Try it',
'who-we-are.title': 'Who we are?',
'who-we-are.description':
'Our goal is to help developers prepare for their first technical interview. Our platform uses AI to simulate real interviews, offering an exceptionally useful and motivating experience.',
'who-we-are.subtitle': 'Our core values drive our growth:',
'who-we-are.tag-1': 'Collaboration',
'who-we-are.tag-2': 'Innovation',
'who-we-are.tag-3': 'Continuous Learning',
'who-we-are.subtitle-2': 'Growing Community',
'who-we-are.description-2': 'We are building a platform that adapts to developers at all stages, fostering growth and mutual learning.',
'who-we-are.subtitle-3': 'Passionate Leadership',
'who-we-are.description-3':
'Led by JamiltonQuintero, a developer with over 5 years of experience and an insatiable hunger for learning, our project is based on a passion for continuous learning and creating a vibrant community.',
'community.title': 'Community',
'contributor.title': 'Contributors',
'footer.title': 'Smart Interviewer',
'footer.description':
'Our goal is to help developers prepare for their first technical interview. Our platform uses artificial intelligence to simulate real interviews, offering an exceptionally useful and engaging experience.',
'footer.contact': 'Contact',
'footer.social-media': 'Social Media',
'footer.terms-and-conditions': 'Terms and Conditions',
'footer.privacy-policy': 'Privacy Policy',
'lenguage-selector': 'Select Language',
},
es: {
'nav.home': 'Inicio',
'nav.services': 'Servicios',
'nav.sample': 'Muestra',
'nav.about': '¿Quienes somos?',
'nav.community': 'Comunidad',
'hero.cvslogan': 'Analiza tu CV, evalúa ofertas laborales y obtén feedback inmediato',
'hero.register': 'Registrarme',
'hero.motivational-quote': '¡Adelante nosotros creemos en tí, tú puedes!',
'hero.demo': 'Demo',
'services.title': 'Servicios',
'services.our-services': 'Nuestros servicios',
'services.card.title': 'Análisis CV',
'services.card.description':
'Analizamos tu CV mediante Inteligencia Artificial (IA) y con ello podemos saber tu nivel de experiencia laboral y habilidades.',
'services.card.video-alt':
'Captura de pantalla de una interfaz de prueba de prototipo. A la izquierda, hay un menú con varias opciones de prueba, incluyendo "Find our Services page", "What was your first impression...", "What would you expect to have...", "Was the Services page where...", "Organize these pages into cat...", y "Which of the following best d...". A la derecha, se muestra la prueba seleccionada con detalles como "Task", "Description", "Prototype", y "Expected path".',
'services.card.title-2': 'Ofertas',
'services.card.description-2':
'Aquí es donde vas a llenar un formulario con la descripción del empleo para preparar el camino a tu entrevista, brindándote confianza y seguridad.',
'services.card.video-alt-2':
'Captura de pantalla de una interfaz de prueba de prototipo con un fondo amarillo degradado. A la izquierda, hay un menú con varias opciones de prueba, incluyendo "Find our Services page", "What was your first impression...", "What would you expect to have...", "Was the Services page where...", "Organize these pages into cat...", y "Which of the following best d...". A la derecha, se muestra la prueba seleccionada con detalles como "Task", "Description", "Prototype", y "Expected path".',
'services.card.title-3': 'Entrevista',
'services.card.description-3':
'Para garantizar la entrevista, hemos asegurado que la información clave, como habilidades y experiencia, coincida en ambos documentos.',
'services.card.video-alt-3':
'Captura de pantalla de una interfaz de prueba de prototipo con un fondo verde degradado. A la izquierda, hay un menú con varias opciones de prueba, incluyendo "Find our Services page", "What was your first impression...", "What would you expect to have...", "Was the Services page where...", "Organize these pages into cat...", y "Which of the following best d...". A la derecha, se muestra la prueba seleccionada con detalles como "Task", "Description", "Prototype", y "Expected path".',
'services.card.title-4': 'Feedback',
'services.card.description-4':
'Una vez completada la entrevista te haremos el feedback de la entrevista y sabrás tus fortalezas y cosas por mejorar.',
'services.card.video-alt-4':
'Captura de pantalla de una interfaz de prueba de prototipo con un fondo azul degradado. A la izquierda, hay un menú con varias opciones de prueba, incluyendo "Find our Services page", "What was your first impression...", "What would you expect to have...", "Was the Services page where...", "Organize these pages into cat...", y "Which of the following best d...". A la derecha, se muestra la prueba seleccionada con detalles como "Task", "Description", "Prototype", y "Expected path".',
'feature.title-2': 'Muestra',
'feature.title': 'Una muestra de las entrevista',
'feature.button': 'Probar',
'who-we-are.title': '¿Quienes somos?',
'who-we-are.description':
'Nuestro objetivo es ayudar a los desarrolladores a prepararse para su primera entrevista técnica. Nuestra plataforma utiliza inteligencia artificial para simular entrevistas reales, ofreciendo una experiencia excepcionalmente útil y cautivadora.',
'who-we-are.subtitle': 'Nuestros valores fundamentales impulsan nuestro crecimiento:',
'who-we-are.tag-1': 'Colaboración',
'who-we-are.tag-2': 'Innovación',
'who-we-are.tag-3': 'Aprendizaje continuo',
'who-we-are.subtitle-2': 'Comunidad en Crecimiento',
'who-we-are.description-2':
'Estamos construyendo una plataforma que se adapta a desarrolladores en todas las etapas, fomentando el crecimiento y el aprendizaje mutuo.',
'who-we-are.subtitle-3': 'Liderazgo Apasionado',
'who-we-are.description-3':
'Liderado por JamiltonQuintero, un desarrollador con más 5 años de experiencia y un hambre infinita de aprendizaje, nuestro proyecto se basa en la pasión por el aprendizaje continuo y la creación de una comunidad vibrante.',
'community.title': 'Comunidad',
'contributor.title': 'Contribuidores',
'footer.title': 'Entrevistador Inteligente',
'footer.description':
'Nuestro objetivo es ayudar a los desarrolladores a prepararse para su primera entrevista técnica. Nuestra plataforma utiliza inteligencia artificial para simular entrevistas reales, ofreciendo una experiencia excepcionalmente útil y cautivadora.',
'footer.contact': 'Contacto',
'footer.social-media': 'Redes sociales',
'footer.terms-and-conditions': 'Términos y condiciones',
'footer.privacy-policy': 'Política de privacidad',
'lenguage-selector': 'Seleccionar idioma',
},
} as const;
20 changes: 20 additions & 0 deletions src/i18n/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { defaultLang, ui, } from './ui';





export function getLangFromUrl(url: URL) {
const basePath = '/home'; // Ajusta esto si cambias el base más adelante
const pathWithoutBase = url.pathname.replace(basePath, ''); // Elimina la base de la URL
const [, lang] = pathWithoutBase.split('/');
if (lang in ui) return lang as keyof typeof ui;
return defaultLang;
}

export function useTranslations(lang: keyof typeof ui) {
return function t(key: keyof (typeof ui)[typeof defaultLang]) {
console.log('Lang:', lang, 'Key:', key, 'Value:', ui[lang][key]);
return ui[lang][key] || ui[defaultLang][key];
};
}
Loading