- 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>
135 lines
4.1 KiB
TypeScript
135 lines
4.1 KiB
TypeScript
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];
|
|
});
|
|
}
|