feat: Simplificar sistema de traducao com LibreTranslate

- Remover traducoes manuais do LanguageContext
- Adicionar componente T para auto-traducao
- Usar useTranslatedContent para conteudo do banco
- Atualizar todas as paginas publicas
- Integrar LibreTranslate para traducao automatica
This commit is contained in:
Erik
2025-11-26 21:33:35 -03:00
parent 6044a437f8
commit ea0c4ac5a6
9 changed files with 313 additions and 703 deletions

View File

@@ -1,71 +1,137 @@
'use client';
import { useEffect, useState, ElementType, ComponentPropsWithoutRef } from 'react';
import { useTranslate } from '@/hooks/useTranslate';
import { useEffect, useState, ReactNode } from 'react';
import { useLanguage, Language } from '@/contexts/LanguageContext';
interface TranslatedTextProps {
text: string;
as?: 'span' | 'p' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'div' | 'li';
// Cache global de traduções
const translationCache = new Map<string, string>();
// Função para traduzir texto via API
async function translateText(text: string, targetLang: string): Promise<string> {
if (!text || text.trim() === '') return text;
const cacheKey = `pt:${targetLang}:${text}`;
if (translationCache.has(cacheKey)) {
return translationCache.get(cacheKey)!;
}
try {
const response = await fetch('/api/translate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, source: 'pt', target: targetLang }),
});
if (response.ok) {
const data = await response.json();
const translated = data.translatedText || text;
translationCache.set(cacheKey, translated);
return translated;
}
} catch (error) {
console.error('Translation error:', error);
}
return text;
}
interface AutoTranslateProps {
children: string;
as?: 'span' | 'p' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'div' | 'li' | 'label';
className?: string;
}
/**
* Componente que traduz texto automaticamente via LibreTranslate
* quando o idioma não é português
* Uso: <T>Texto em português</T>
*/
export function TranslatedText({ text, as = 'span', className }: TranslatedTextProps) {
const { translate, language } = useTranslate();
const [translatedText, setTranslatedText] = useState(text);
const [isLoading, setIsLoading] = useState(false);
export function T({ children, as = 'span', className }: AutoTranslateProps) {
const { language } = useLanguage();
const [translatedText, setTranslatedText] = useState(children);
useEffect(() => {
if (language === 'PT') {
setTranslatedText(text);
setTranslatedText(children);
return;
}
let cancelled = false;
setIsLoading(true);
const targetLang = language.toLowerCase();
translate(text).then((result) => {
translateText(children, targetLang).then((result) => {
if (!cancelled) {
setTranslatedText(result);
setIsLoading(false);
}
});
return () => {
cancelled = true;
};
}, [text, language, translate]);
}, [children, language]);
const Tag = as;
return <Tag className={className}>{translatedText}</Tag>;
}
return (
<Tag className={className} data-translating={isLoading}>
{translatedText}
</Tag>
);
// Alias para uso mais curto
export const AutoTranslate = T;
/**
* Hook para traduzir texto programaticamente
*/
export function useTranslate() {
const { language } = useLanguage();
const [isTranslating, setIsTranslating] = useState(false);
const translate = async (text: string): Promise<string> => {
if (!text || language === 'PT') return text;
setIsTranslating(true);
try {
const result = await translateText(text, language.toLowerCase());
return result;
} finally {
setIsTranslating(false);
}
};
const translateBatch = async (texts: string[]): Promise<string[]> => {
if (language === 'PT') return texts;
setIsTranslating(true);
try {
const results = await Promise.all(
texts.map(text => translateText(text, language.toLowerCase()))
);
return results;
} finally {
setIsTranslating(false);
}
};
return { translate, translateBatch, isTranslating, language };
}
/**
* Hook para traduzir objetos de conteúdo do banco de dados
* Hook para traduzir conteúdo do banco de dados
*/
export function useTranslatedContent<T extends Record<string, unknown>>(content: T): {
translatedContent: T;
isTranslating: boolean
export function useTranslatedContent<T extends Record<string, unknown>>(content: T | null): {
translatedContent: T | null;
isTranslating: boolean;
} {
const { translateBatch, language } = useTranslate();
const [translatedContent, setTranslatedContent] = useState<T>(content);
const { language } = useLanguage();
const [translatedContent, setTranslatedContent] = useState<T | null>(content);
const [isTranslating, setIsTranslating] = useState(false);
useEffect(() => {
if (language === 'PT') {
if (!content || language === 'PT') {
setTranslatedContent(content);
return;
}
let cancelled = false;
const targetLang = language.toLowerCase();
const translateContent = async () => {
setIsTranslating(true);
@@ -75,13 +141,15 @@ export function useTranslatedContent<T extends Record<string, unknown>>(content:
const paths: string[] = [];
const extractTexts = (obj: unknown, path: string = '') => {
if (typeof obj === 'string' && obj.length > 0) {
if (typeof obj === 'string' && obj.length > 0 && obj.length < 5000) {
texts.push(obj);
paths.push(path);
} else if (Array.isArray(obj)) {
obj.forEach((item, index) => extractTexts(item, `${path}[${index}]`));
} else if (obj && typeof obj === 'object') {
Object.entries(obj).forEach(([key, value]) => {
// Ignorar campos que não devem ser traduzidos
if (['icon', 'image', 'img', 'url', 'href', 'id', 'slug'].includes(key)) return;
extractTexts(value, path ? `${path}.${key}` : key);
});
}
@@ -95,7 +163,10 @@ export function useTranslatedContent<T extends Record<string, unknown>>(content:
}
try {
const translations = await translateBatch(texts);
// Traduzir todos os textos
const translations = await Promise.all(
texts.map(text => translateText(text, targetLang))
);
if (cancelled) return;
@@ -128,7 +199,7 @@ export function useTranslatedContent<T extends Record<string, unknown>>(content:
return () => {
cancelled = true;
};
}, [content, language, translateBatch]);
}, [content, language]);
return { translatedContent, isTranslating };
}