UNPKG

@hkxdv/astro-shade-dx-template

Version:

Astro template with React, shadcn/ui, Bun, Biome & ESLint. Pre-configured TypeScript, Tailwind CSS, dark/light themes, and optimized DX. Run directly with 'bunx @hkxdv/astro-shade-dx-template' without installation.

213 lines (182 loc) 6.57 kB
/** * Sistema de traducciones para la aplicación que utiliza los archivos JSON de /locales * @module translations */ import * as React from "react"; import { useLanguage, type SupportedLocale, DEFAULT_LOCALE, } from "@/contexts/LanguageContext"; import { getLocaleTranslations } from "@/lib/i18n-utils"; // Cargar traducciones estáticas para resolver problemas de hidratación import commonES from "@/locales/es/common.json"; import featuresES from "@/locales/es/features.json"; import commonEN from "@/locales/en/common.json"; import featuresEN from "@/locales/en/features.json"; // Inicialización de traducciones estáticas para SSR const STATIC_TRANSLATIONS: Record<string, unknown> = { es: { ...commonES, ...featuresES }, en: { ...commonEN, ...featuresEN }, }; /** * Clase para gestionar las traducciones con caché optimizada */ class TranslationManager { // Caché para las traducciones cargadas por idioma private translations: Record<string, unknown> = { // Inicializamos con traducciones estáticas ...STATIC_TRANSLATIONS, }; // Caché para traducciones específicas de pares idioma:clave private translationValues: Record<string, string> = {}; // Constructor que inicia carga de traducciones por defecto constructor() { // Iniciar carga de traducciones para el idioma por defecto if (typeof window !== "undefined") { this.loadLanguage(DEFAULT_LOCALE).catch(console.error); } } /** * Verifica si un idioma está cargado en la caché */ hasLanguage(language: string): boolean { return Boolean(this.translations[language]); } /** * Carga traducciones para un idioma específico */ async loadLanguage(language: string): Promise<void> { if (this.hasLanguage(language)) return; try { const translations = await getLocaleTranslations(language); this.translations[language] = translations; } catch (error) { console.error(`Error cargando traducciones para ${language}:`, error); } } /** * Obtiene una traducción específica, usando cachés para optimizar rendimiento */ getTranslation( key: string, language: SupportedLocale = DEFAULT_LOCALE ): string { const cacheKey = `${language}:${key}`; // Verificar caché de valores primero if (this.translationValues[cacheKey]) { return this.translationValues[cacheKey]; } // Obtener y almacenar el valor const value = this.findTranslationValue(key, language); this.translationValues[cacheKey] = value; return value; } /** * Busca el valor de una traducción en los diccionarios cargados */ private findTranslationValue(key: string, language: SupportedLocale): string { try { const keys = key.split("."); // Verificar si tenemos las traducciones para este idioma if (!this.translations[language]) { // Si no tenemos traducciones para este idioma, usar el por defecto if (language !== DEFAULT_LOCALE && this.translations[DEFAULT_LOCALE]) { return this.getTranslation(key, DEFAULT_LOCALE); } // Si no tenemos ni el idioma solicitado ni el por defecto, devolver la clave return key; } // Obtener el diccionario del idioma correspondiente const translations = this.translations[language]; let result: unknown = translations; // Navegar a través del objeto de traducciones usando las claves for (const k of keys) { if ( !result || typeof result !== "object" || !(k in (result as Record<string, unknown>)) ) { // Si no existe en el idioma solicitado, intentar con el idioma por defecto if (language !== DEFAULT_LOCALE) { return this.getTranslation(key, DEFAULT_LOCALE); } return key; // Fallback a la clave si no se encuentra } result = (result as Record<string, unknown>)[k]; } // Si el resultado es un string, devolverlo if (typeof result === "string") { return result; } return key; // Fallback final } catch (error) { // En caso de error, devolver la clave original return key; } } /** * Limpia las cachés */ clearCache(): void { this.translationValues = {}; } } // Instancia única del gestor de traducciones const translationManager = new TranslationManager(); /** * Verifica si el código se está ejecutando en el navegador * @returns {boolean} true si estamos en el navegador, false si estamos en el servidor */ const isBrowser = () => typeof window !== "undefined" && typeof document !== "undefined"; /** * Precarga traducciones para todos los idiomas soportados */ export function preloadAllTranslations() { const locales = ["es", "en"]; for (const locale of locales) { translationManager.loadLanguage(locale).catch(console.error); } } /** * Carga dinámicamente las traducciones para un idioma si no están en caché * @param {string} language - Idioma a cargar */ export async function loadTranslations(language: string): Promise<void> { await translationManager.loadLanguage(language); } /** * Obtener una traducción basada en una clave y un idioma * @param {string} key - Clave de traducción, puede ser anidada usando puntos (ej: "theme.dark") * @param {SupportedLocale} language - Idioma en el que se desea obtener la traducción * @returns {string} Texto traducido o la clave original si no se encuentra */ export function getTranslation( key: string, language: SupportedLocale = DEFAULT_LOCALE ): string { return translationManager.getTranslation(key, language); } /** * Hook personalizado para usar traducciones en componentes React * Utiliza el contexto de idioma * @returns {Object} Objeto con función t para traducir y el idioma actual */ export function useTranslation() { const { currentLocale } = useLanguage(); // Obtener el idioma del contexto // Cargar traducciones si no están cargadas React.useEffect(() => { if (!translationManager.hasLanguage(currentLocale)) { loadTranslations(currentLocale).catch(console.error); } }, [currentLocale]); const t = React.useCallback( (key: string) => getTranslation(key, currentLocale), [currentLocale] ); return { t, language: currentLocale, }; }