@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
text/typescript
/**
* 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,
};
}