Initial commit: AutonetSellCar platform with deployment system
- Frontend: Next.js 14 with TypeScript - Backend: FastAPI with SQLAlchemy - Agent: Carmodoo sync agent - Deployment: Docker Compose based staging/production setup - Scripts: Automated deployment with rollback support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
134
frontend/src/lib/useTranslate.ts
Normal file
134
frontend/src/lib/useTranslate.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { translationsApi } from './api';
|
||||
import { useLanguageStore, translateCarName, Language } from './i18n';
|
||||
|
||||
// Cache for translations to avoid repeated API calls
|
||||
const translationCache: Record<string, Record<string, string>> = {};
|
||||
|
||||
export function useTranslate() {
|
||||
const { language } = useLanguageStore();
|
||||
const [translations, setTranslations] = useState<Record<string, string>>({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Get cache key for current language
|
||||
const cacheKey = `trans_${language}`;
|
||||
|
||||
// Load translations from cache on mount
|
||||
useEffect(() => {
|
||||
if (translationCache[cacheKey]) {
|
||||
setTranslations(translationCache[cacheKey]);
|
||||
}
|
||||
}, [cacheKey]);
|
||||
|
||||
// Translate a single text
|
||||
const translate = useCallback((text: string | undefined | null): string => {
|
||||
if (!text) return '';
|
||||
if (language === 'ko') return text; // Korean is source, no translation needed
|
||||
|
||||
// Try static translations FIRST (for fuel, transmission, car names, etc.)
|
||||
const staticTranslation = translateCarName(text, language as Language);
|
||||
if (staticTranslation !== text) {
|
||||
return staticTranslation;
|
||||
}
|
||||
|
||||
// Then check API cache for other translations
|
||||
const cached = translationCache[cacheKey]?.[text];
|
||||
if (cached) return cached;
|
||||
|
||||
return text; // Fallback to original if no translation found
|
||||
}, [language, cacheKey]);
|
||||
|
||||
// Bulk load translations for multiple texts
|
||||
const loadTranslations = useCallback(async (texts: string[], category?: string) => {
|
||||
if (language === 'ko') return; // No need to translate Korean
|
||||
|
||||
// Filter out already cached texts
|
||||
const uncachedTexts = texts.filter(
|
||||
t => t && !translationCache[cacheKey]?.[t]
|
||||
);
|
||||
|
||||
if (uncachedTexts.length === 0) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
// Map language code to API expected format
|
||||
const langCode = language === 'mn' ? 'mn' : language === 'ru' ? 'ru' : 'en';
|
||||
|
||||
const result = await translationsApi.bulkLookup(uncachedTexts, langCode, category);
|
||||
|
||||
// Update cache
|
||||
if (!translationCache[cacheKey]) {
|
||||
translationCache[cacheKey] = {};
|
||||
}
|
||||
|
||||
Object.assign(translationCache[cacheKey], result.translations);
|
||||
setTranslations({ ...translationCache[cacheKey] });
|
||||
} catch (err) {
|
||||
console.error('Failed to load translations:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [language, cacheKey]);
|
||||
|
||||
// Translate car object fields
|
||||
const translateCar = useCallback((car: {
|
||||
car_name?: string;
|
||||
fuel?: string;
|
||||
transmission?: string;
|
||||
color?: string;
|
||||
maker?: { name: string };
|
||||
model?: { name: string };
|
||||
}) => {
|
||||
return {
|
||||
car_name: translate(car.car_name),
|
||||
fuel: translate(car.fuel),
|
||||
transmission: translate(car.transmission),
|
||||
color: translate(car.color),
|
||||
maker_name: translate(car.maker?.name),
|
||||
model_name: translate(car.model?.name),
|
||||
};
|
||||
}, [translate]);
|
||||
|
||||
// Preload translations for a list of cars
|
||||
const preloadCarTranslations = useCallback(async (cars: Array<{
|
||||
car_name?: string;
|
||||
fuel?: string;
|
||||
transmission?: string;
|
||||
color?: string;
|
||||
maker?: { name: string };
|
||||
model?: { name: string };
|
||||
}>) => {
|
||||
const textsToTranslate: string[] = [];
|
||||
|
||||
cars.forEach(car => {
|
||||
if (car.car_name) textsToTranslate.push(car.car_name);
|
||||
if (car.fuel) textsToTranslate.push(car.fuel);
|
||||
if (car.transmission) textsToTranslate.push(car.transmission);
|
||||
if (car.color) textsToTranslate.push(car.color);
|
||||
if (car.maker?.name) textsToTranslate.push(car.maker.name);
|
||||
if (car.model?.name) textsToTranslate.push(car.model.name);
|
||||
});
|
||||
|
||||
// Remove duplicates
|
||||
const uniqueTexts = Array.from(new Set(textsToTranslate));
|
||||
|
||||
if (uniqueTexts.length > 0) {
|
||||
await loadTranslations(uniqueTexts);
|
||||
}
|
||||
}, [loadTranslations]);
|
||||
|
||||
return {
|
||||
translate,
|
||||
translateCar,
|
||||
loadTranslations,
|
||||
preloadCarTranslations,
|
||||
loading,
|
||||
};
|
||||
}
|
||||
|
||||
// Clear translation cache (useful when translations are updated)
|
||||
export function clearTranslationCache() {
|
||||
Object.keys(translationCache).forEach(key => {
|
||||
delete translationCache[key];
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user