475 lines
16 KiB
TypeScript
475 lines
16 KiB
TypeScript
import { createContext, useContext, useEffect, useState } from 'react';
|
||
import { Facebook, Instagram, Linkedin, Youtube } from 'lucide-react';
|
||
import type { CSSProperties, ReactNode } from 'react';
|
||
|
||
export const tempoSocialLinks = [
|
||
{
|
||
name: 'Facebook',
|
||
href: 'https://www.facebook.com/revistatempo.mz/',
|
||
icon: Facebook,
|
||
color: '#1877f2',
|
||
},
|
||
{
|
||
name: 'Instagram',
|
||
href: 'https://www.instagram.com/revistatempo.mz/',
|
||
icon: Instagram,
|
||
color: '#e1306c',
|
||
},
|
||
{
|
||
name: 'LinkedIn',
|
||
href: 'https://www.linkedin.com/company/revistatempo/',
|
||
icon: Linkedin,
|
||
color: '#38a5df',
|
||
},
|
||
{
|
||
name: 'YouTube',
|
||
href: 'https://www.youtube.com/@RevistaTempoMz/',
|
||
icon: Youtube,
|
||
color: '#ff3333',
|
||
},
|
||
];
|
||
|
||
const menuTranslations = [
|
||
['Sobre nós', 'About us', 'À propos', 'Sobre nosotros'],
|
||
['Revista Tempo', 'Revista Tempo', 'Revista Tempo', 'Revista Tempo'],
|
||
[
|
||
'Abre num novo separador',
|
||
'Opens in a new tab',
|
||
'Ouvre un nouvel onglet',
|
||
'Abre en una nueva pestaña',
|
||
],
|
||
['O meu painel', 'My dashboard', 'Mon espace', 'Mi panel'],
|
||
[
|
||
'Manifestar interesse',
|
||
'Express interest',
|
||
'Manifester un intérêt',
|
||
'Manifestar interés',
|
||
],
|
||
[
|
||
'pedido recebido',
|
||
'request received',
|
||
'demande reçue',
|
||
'solicitud recibida',
|
||
],
|
||
[
|
||
'pedidos recebidos',
|
||
'requests received',
|
||
'demandes reçues',
|
||
'solicitudes recibidas',
|
||
],
|
||
[
|
||
'Porquê investir em Moçambique?',
|
||
'Why invest in Mozambique?',
|
||
'Pourquoi investir au Mozambique ?',
|
||
'Por qué invertir en Mozambique?',
|
||
],
|
||
[
|
||
'Partir de uma ideia',
|
||
'Start with an idea',
|
||
'Partir d’une idée',
|
||
'Partir de una idea',
|
||
],
|
||
[
|
||
'Consultar oportunidades',
|
||
'Browse opportunities',
|
||
'Consulter les opportunités',
|
||
'Consultar oportunidades',
|
||
],
|
||
[
|
||
'Simulador Fiscal',
|
||
'Tax simulator',
|
||
'Simulateur fiscal',
|
||
'Simulador fiscal',
|
||
],
|
||
[
|
||
'Oportunidades de investimento',
|
||
'Investment opportunities',
|
||
'Opportunités d’investissement',
|
||
'Oportunidades de inversión',
|
||
],
|
||
[
|
||
'Sectores de investimento',
|
||
'Investment sectors',
|
||
'Secteurs d’investissement',
|
||
'Sectores de inversión',
|
||
],
|
||
['Localizações', 'Locations', 'Localisations', 'Ubicaciones'],
|
||
[
|
||
'Iniciar investimento',
|
||
'Start an investment',
|
||
'Démarrer un investissement',
|
||
'Iniciar una inversión',
|
||
],
|
||
[
|
||
'Incentivos e simulador fiscal',
|
||
'Incentives and tax simulator',
|
||
'Incitations et simulateur fiscal',
|
||
'Incentivos y simulador fiscal',
|
||
],
|
||
['Licenciamento', 'Licensing', 'Autorisations', 'Licencias'],
|
||
['Acesso à terra', 'Land access', 'Accès à la terre', 'Acceso a la tierra'],
|
||
[
|
||
'Serviços ao investidor',
|
||
'Investor services',
|
||
'Services aux investisseurs',
|
||
'Servicios al inversor',
|
||
],
|
||
[
|
||
'Acompanhamento do projecto',
|
||
'Project follow-up',
|
||
'Suivi du projet',
|
||
'Seguimiento del proyecto',
|
||
],
|
||
[
|
||
'Apoio ao investidor',
|
||
'Investor support',
|
||
'Assistance aux investisseurs',
|
||
'Apoyo al inversor',
|
||
],
|
||
[
|
||
'Dossiers e percursos',
|
||
'Investment files and journeys',
|
||
'Dossiers et parcours',
|
||
'Expedientes y recorridos',
|
||
],
|
||
[
|
||
'Perfil e empresas',
|
||
'Profile and companies',
|
||
'Profil et entreprises',
|
||
'Perfil y empresas',
|
||
],
|
||
[
|
||
'Pedidos e tarefas de acompanhamento',
|
||
'Requests and follow-up tasks',
|
||
'Demandes et tâches de suivi',
|
||
'Solicitudes y tareas de seguimiento',
|
||
],
|
||
[
|
||
'Manifestações e favoritos',
|
||
'Expressions of interest and favourites',
|
||
'Manifestations d’intérêt et favoris',
|
||
'Manifestaciones de interés y favoritos',
|
||
],
|
||
['Notificações', 'Notifications', 'Notifications', 'Notificaciones'],
|
||
['A minha atividade', 'My activity', 'Mes activités', 'Mi actividad'],
|
||
[
|
||
'As minhas parcerias',
|
||
'My partnerships',
|
||
'Mes partenariats',
|
||
'Mis alianzas',
|
||
],
|
||
['Os meus negócios', 'My businesses', 'Mes entreprises', 'Mis negocios'],
|
||
];
|
||
const translatedMenu = (column: number) =>
|
||
Object.fromEntries(menuTranslations.map((row) => [row[0], row[column]]));
|
||
|
||
const translations: Record<string, Record<string, string>> = {
|
||
en: {
|
||
...translatedMenu(1),
|
||
Descoberta: 'Discover',
|
||
Realização: 'Invest',
|
||
Suporte: 'Support',
|
||
Entrar: 'Sign in',
|
||
'A minha área': 'My account',
|
||
'Terminar sessão': 'Sign out',
|
||
Contactos: 'Contacts',
|
||
Navegação: 'Navigation',
|
||
Início: 'Home',
|
||
Oportunidades: 'Opportunities',
|
||
Sectores: 'Sectors',
|
||
Legislações: 'Legislation',
|
||
'Guia do investidor': 'Investor guide',
|
||
Contacto: 'Contact',
|
||
'Política de Privacidade': 'Privacy Policy',
|
||
'Termos de Uso': 'Terms of Use',
|
||
'Todos os direitos reservados.': 'All rights reserved.',
|
||
'Siga a Revista Tempo para actualizações exclusivas.':
|
||
'Follow Revista Tempo for exclusive updates.',
|
||
'Número de registo': 'Registration number',
|
||
'A minha conta e atividade': 'My account and activity',
|
||
'Área de trabalho': 'Workspace',
|
||
},
|
||
fr: {
|
||
...translatedMenu(2),
|
||
Descoberta: 'Découvrir',
|
||
Realização: 'Investir',
|
||
Suporte: 'Assistance',
|
||
Entrar: 'Connexion',
|
||
'A minha área': 'Mon compte',
|
||
'Terminar sessão': 'Déconnexion',
|
||
Contactos: 'Contacts',
|
||
Navegação: 'Navigation',
|
||
Início: 'Accueil',
|
||
Oportunidades: 'Opportunités',
|
||
Sectores: 'Secteurs',
|
||
Legislações: 'Législation',
|
||
'Guia do investidor': 'Guide de l’investisseur',
|
||
Contacto: 'Contact',
|
||
'Política de Privacidade': 'Politique de confidentialité',
|
||
'Termos de Uso': 'Conditions d’utilisation',
|
||
'Todos os direitos reservados.': 'Tous droits réservés.',
|
||
'Siga a Revista Tempo para actualizações exclusivas.':
|
||
'Suivez Revista Tempo pour des actualités exclusives.',
|
||
'Número de registo': 'Numéro d’enregistrement',
|
||
'A minha conta e atividade': 'Mon compte et mes activités',
|
||
'Área de trabalho': 'Espace de travail',
|
||
},
|
||
es: {
|
||
...translatedMenu(3),
|
||
Descoberta: 'Descubrir',
|
||
Realização: 'Invertir',
|
||
Suporte: 'Soporte',
|
||
Entrar: 'Entrar',
|
||
'A minha área': 'Mi cuenta',
|
||
'Terminar sessão': 'Cerrar sesión',
|
||
Contactos: 'Contactos',
|
||
Navegação: 'Navegación',
|
||
Início: 'Inicio',
|
||
Oportunidades: 'Oportunidades',
|
||
Sectores: 'Sectores',
|
||
Legislações: 'Legislación',
|
||
'Guia do investidor': 'Guía del inversor',
|
||
Contacto: 'Contacto',
|
||
'Política de Privacidade': 'Política de privacidad',
|
||
'Termos de Uso': 'Términos de uso',
|
||
'Todos os direitos reservados.': 'Todos los derechos reservados.',
|
||
'Siga a Revista Tempo para actualizações exclusivas.':
|
||
'Siga Revista Tempo para recibir novedades exclusivas.',
|
||
'Número de registo': 'Número de registro',
|
||
'A minha conta e atividade': 'Mi cuenta y actividad',
|
||
'Área de trabalho': 'Área de trabajo',
|
||
},
|
||
};
|
||
|
||
const sharedTranslations: Record<string, [string, string, string]> = {
|
||
'Criar conta': ['Create account', 'Créer un compte', 'Crear cuenta'],
|
||
'Entrar na sua conta': [
|
||
'Sign in to your account',
|
||
'Connectez-vous à votre compte',
|
||
'Inicie sesión en su cuenta',
|
||
],
|
||
'Área reservada': ['Private area', 'Espace réservé', 'Área reservada'],
|
||
'Bolsa de Parcerias, Investidores e Capital': [
|
||
'Partnership, Investor and Capital Exchange',
|
||
'Bourse des partenariats, investisseurs et capitaux',
|
||
'Bolsa de Alianzas, Inversores y Capital',
|
||
],
|
||
'Aceda à sua conta Tempo.': [
|
||
'Access your Tempo account.',
|
||
'Accédez à votre compte Tempo.',
|
||
'Acceda a su cuenta Tempo.',
|
||
],
|
||
'Crie uma conta simples para continuar e acompanhar a sua operação. Não é necessário configurar MFA para participar.':
|
||
[
|
||
'Create an account to continue and follow your operation. You do not need to set up MFA to participate.',
|
||
'Créez un compte pour poursuivre et suivre votre opération. La configuration de l’authentification multifacteur n’est pas nécessaire pour participer.',
|
||
'Cree una cuenta para continuar y seguir su operación. No necesita configurar la autenticación multifactor para participar.',
|
||
],
|
||
'Entre para continuar a operação que escolheu. Os dados preenchidos permanecem guardados neste navegador.':
|
||
[
|
||
'Sign in to continue the operation you chose. Your entered data remains saved in this browser.',
|
||
'Connectez-vous pour poursuivre l’opération choisie. Les données saisies restent enregistrées dans ce navigateur.',
|
||
'Inicie sesión para continuar la operación elegida. Los datos introducidos permanecen guardados en este navegador.',
|
||
],
|
||
'Palavra-passe': ['Password', 'Mot de passe', 'Contraseña'],
|
||
'Mostrar palavra-passe': [
|
||
'Show password',
|
||
'Afficher le mot de passe',
|
||
'Mostrar contraseña',
|
||
],
|
||
'Ocultar palavra-passe': [
|
||
'Hide password',
|
||
'Masquer le mot de passe',
|
||
'Ocultar contraseña',
|
||
],
|
||
'Confirmar palavra-passe': [
|
||
'Confirm password',
|
||
'Confirmer le mot de passe',
|
||
'Confirmar contraseña',
|
||
],
|
||
Nome: ['Name', 'Nom', 'Nombre'],
|
||
'Nome completo': ['Full name', 'Nom complet', 'Nombre completo'],
|
||
'Esqueceu a palavra-passe?': [
|
||
'Forgot your password?',
|
||
'Mot de passe oublié ?',
|
||
'¿Olvidó su contraseña?',
|
||
],
|
||
'Manter sessão iniciada': [
|
||
'Keep me signed in',
|
||
'Rester connecté',
|
||
'Mantener la sesión iniciada',
|
||
],
|
||
'Ainda não tem conta?': [
|
||
'Don’t have an account?',
|
||
'Vous n’avez pas de compte ?',
|
||
'¿Aún no tiene cuenta?',
|
||
],
|
||
'Já tem conta?': [
|
||
'Already have an account?',
|
||
'Vous avez déjà un compte ?',
|
||
'¿Ya tiene cuenta?',
|
||
],
|
||
'Entrar com chave de acesso': [
|
||
'Sign in with a passkey',
|
||
'Se connecter avec une clé d’accès',
|
||
'Iniciar sesión con clave de acceso',
|
||
],
|
||
'A autenticar…': [
|
||
'Signing in…',
|
||
'Connexion en cours…',
|
||
'Iniciando sesión…',
|
||
],
|
||
'Ou continuar com email': [
|
||
'Or continue with email',
|
||
'Ou continuer avec l’e-mail',
|
||
'O continuar con correo electrónico',
|
||
],
|
||
'O email ou a palavra-passe não correspondem aos nossos registos.': [
|
||
'The email or password does not match our records.',
|
||
'L’adresse e-mail ou le mot de passe ne correspond pas à nos informations.',
|
||
'El correo electrónico o la contraseña no coinciden con nuestros registros.',
|
||
],
|
||
'A palavra-passe está incorrecta.': [
|
||
'The password is incorrect.',
|
||
'Le mot de passe est incorrect.',
|
||
'La contraseña es incorrecta.',
|
||
],
|
||
'Atalho para o conteúdo': [
|
||
'Skip link',
|
||
'Lien d’accès rapide',
|
||
'Enlace de acceso rápido',
|
||
],
|
||
'Saltar para o conteúdo': [
|
||
'Skip to content',
|
||
'Aller au contenu',
|
||
'Ir al contenido',
|
||
],
|
||
'Caminho de navegação': [
|
||
'Breadcrumb',
|
||
'Fil d’Ariane',
|
||
'Ruta de navegación',
|
||
],
|
||
'Contacto de apoio ao investidor': [
|
||
'Investor support contact',
|
||
'Contact de l’assistance aux investisseurs',
|
||
'Contacto de apoyo al inversor',
|
||
],
|
||
'Contactar Apoio ao Investidor': [
|
||
'Contact investor support',
|
||
'Contacter l’assistance aux investisseurs',
|
||
'Contactar con apoyo al inversor',
|
||
],
|
||
'Ambiente de homologação': [
|
||
'Review environment',
|
||
'Environnement de validation',
|
||
'Entorno de validación',
|
||
],
|
||
'Homologação reservada à revisão da Tempo. Consulte o conteúdo público; entre na sua conta quando iniciar uma operação.':
|
||
[
|
||
'This review environment is reserved for Tempo. Browse public content; sign in when starting an operation.',
|
||
'Cet environnement de validation est réservé à Tempo. Consultez le contenu public ; connectez-vous pour commencer une opération.',
|
||
'Este entorno de validación está reservado para Tempo. Consulte el contenido público; inicie sesión al comenzar una operación.',
|
||
],
|
||
'Idioma da navegação': [
|
||
'Navigation language',
|
||
'Langue de navigation',
|
||
'Idioma de navegación',
|
||
],
|
||
'Navegação institucional': ['Language', 'Langue', 'Idioma'],
|
||
Valores: ['Values', 'Valeurs', 'Valores'],
|
||
'Ver menos': ['Show less', 'Voir moins', 'Ver menos'],
|
||
'Ver mais': ['Show more', 'Voir plus', 'Ver más'],
|
||
Voltar: ['Back', 'Retour', 'Volver'],
|
||
'Anos de Experiência': [
|
||
'Years of experience',
|
||
'Années d’expérience',
|
||
'Años de experiencia',
|
||
],
|
||
'Projetos Realizados': [
|
||
'Projects completed',
|
||
'Projets réalisés',
|
||
'Proyectos realizados',
|
||
],
|
||
};
|
||
for (const [source, values] of Object.entries(sharedTranslations)) {
|
||
(['en', 'fr', 'es'] as const).forEach((locale, index) => {
|
||
translations[locale][source] = values[index];
|
||
});
|
||
}
|
||
|
||
const TempoUi = createContext({
|
||
language: 'pt',
|
||
setLanguage: (_language: string) => {},
|
||
t: (text: string) => text,
|
||
requestCount: 0,
|
||
registerSubmittedInterest: () => {},
|
||
});
|
||
|
||
export function TempoUiProvider({ children }: { children: ReactNode }) {
|
||
const [language, setCurrentLanguage] = useState('pt');
|
||
const [requestCount, setRequestCount] = useState(0);
|
||
useEffect(() => {
|
||
try {
|
||
const saved = localStorage.getItem('tempo-navigation-language');
|
||
if (saved && /^[a-zA-Z][a-zA-Z0-9-]{0,19}$/.test(saved))
|
||
setCurrentLanguage(saved);
|
||
} catch {
|
||
/* Navigation remains usable without browser storage. */
|
||
}
|
||
}, []);
|
||
useEffect(() => {
|
||
document.documentElement.lang = language;
|
||
}, [language]);
|
||
const setLanguage = (next: string) => {
|
||
if (!/^[a-zA-Z][a-zA-Z0-9-]{0,19}$/.test(next)) return;
|
||
setCurrentLanguage(next);
|
||
try {
|
||
localStorage.setItem('tempo-navigation-language', next);
|
||
} catch {
|
||
/* Optional persistence. */
|
||
}
|
||
};
|
||
return (
|
||
<TempoUi.Provider
|
||
value={{
|
||
language,
|
||
setLanguage,
|
||
t: (text) => translations[language]?.[text] ?? text,
|
||
requestCount,
|
||
registerSubmittedInterest: () =>
|
||
setRequestCount((count) => count + 1),
|
||
}}
|
||
>
|
||
{children}
|
||
</TempoUi.Provider>
|
||
);
|
||
}
|
||
|
||
export const useTempoUi = () => useContext(TempoUi);
|
||
|
||
export function TempoSocialLinks({ footer = false }: { footer?: boolean }) {
|
||
return (
|
||
<div
|
||
className="flex items-center gap-1"
|
||
aria-label="Redes sociais da Revista Tempo"
|
||
>
|
||
{tempoSocialLinks.map(({ name, href, icon: Icon, color }) => (
|
||
<a
|
||
key={name}
|
||
href={href}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
aria-label={`Revista Tempo — ${name}`}
|
||
title={name}
|
||
className={
|
||
footer ? 'tempo-footer-social' : 'tempo-header-social'
|
||
}
|
||
style={{ '--social-color': color } as CSSProperties}
|
||
>
|
||
<Icon size={footer ? 19 : 16} aria-hidden="true" />
|
||
</a>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|