UNPKG

@gftdcojp/gftd-orm

Version:

Enterprise-grade real-time data platform with ksqlDB, inspired by Supabase architecture

492 lines 17.6 kB
/** * 翻訳機能用Reactフック */ import { useEffect, useState, useCallback, useMemo } from 'react'; // @todo [P2] Import from actual Weblate SDK when available // Assigned: Team Lead | Milestone: Q1 2025 | Impact: Translation features // import { useTranslation as useWeblateTranslation, useLanguage as useWeblateLanguage } from '@gftdcojp/weblate-nextjs-sdk'; import { TranslatorClient, translatorConfig } from '../translator-client'; import { useGftdOrm } from './useGftdOrm'; import { log } from '../utils/logger'; /** * 翻訳機能フック */ export function useTranslator(options = {}) { const { language: initialLanguage, namespace = 'default', tenantSpecific = true, autoRefresh = false, refreshInterval = 30000, fallbackLanguage = 'en', user, ormConfig = {}, } = options; const { client } = useGftdOrm(ormConfig); // @todo [P2] Replace with actual Weblate SDK when available // Assigned: Frontend Dev | Milestone: Q1 2025 | Impact: Hook functionality const weblateT = useCallback((key, params) => key, []); const weblateLanguage = initialLanguage || 'en'; const setWeblateLanguage = useCallback((lang) => { // @todo [P2] Implement when Weblate SDK is available // Assigned: Frontend Dev | Milestone: Q1 2025 | Impact: Language switching log.info('Setting language to:', lang); }, []); const [translatorClient, setTranslatorClient] = useState(null); const [currentLanguage, setCurrentLanguage] = useState(initialLanguage || weblateLanguage || 'en'); const [translations, setTranslations] = useState([]); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [initialized, setInitialized] = useState(false); // 翻訳クライアントを初期化 useEffect(() => { if (!initialized) { try { const config = translatorConfig.fromEnv(); const client = TranslatorClient.getInstance(config); if (user) { client.setCurrentUser(user); } setTranslatorClient(client); setInitialized(true); log.info('Translator client initialized in hook'); } catch (error) { log.error(`Failed to initialize translator client: ${error}`); setError(String(error)); } } }, [initialized, user]); // 言語変更時にWeblateの言語も更新 useEffect(() => { if (currentLanguage !== weblateLanguage) { setWeblateLanguage(currentLanguage); } }, [currentLanguage, weblateLanguage, setWeblateLanguage]); // 翻訳データを取得 const fetchTranslations = useCallback(async (lang) => { if (!translatorClient) return; const targetLanguage = lang || currentLanguage; setLoading(true); setError(null); try { const response = await translatorClient.getTranslations(targetLanguage, { limit: 1000, }); setTranslations(response.results); log.info(`Fetched ${response.results.length} translations for language: ${targetLanguage}`); } catch (error) { log.error(`Failed to fetch translations: ${error}`); setError(String(error)); } finally { setLoading(false); } }, [translatorClient, currentLanguage]); // 統計情報を取得 const fetchStats = useCallback(async (lang) => { if (!translatorClient) return; const targetLanguage = lang || currentLanguage; try { const stats = await translatorClient.getTranslationStats(targetLanguage); setStats(stats); log.info(`Fetched translation stats for language: ${targetLanguage}`); } catch (error) { log.error(`Failed to fetch translation stats: ${error}`); setError(String(error)); } }, [translatorClient, currentLanguage]); // 初期データロード useEffect(() => { if (translatorClient && initialized) { fetchTranslations(); fetchStats(); } }, [translatorClient, initialized, fetchTranslations, fetchStats]); // 自動更新 useEffect(() => { if (!autoRefresh || !translatorClient) return; const interval = setInterval(() => { fetchTranslations(); fetchStats(); }, refreshInterval); return () => clearInterval(interval); }, [autoRefresh, translatorClient, refreshInterval, fetchTranslations, fetchStats]); // 翻訳関数 const t = useCallback((key, params, options) => { const { namespace: ns = namespace, fallback, interpolation = true, } = options || {}; // まずWeblateの翻訳を試す try { const translation = weblateT(key, params); if (translation && translation !== key) { return translation; } } catch (error) { log.warn(`Weblate translation failed for key: ${key}`); } // ローカルの翻訳データから検索 const localTranslation = translations.find(item => item.key === key || item.key === `${ns}.${key}`); if (localTranslation && localTranslation.translated && localTranslation.text) { let result = localTranslation.text; // パラメータ補間 if (interpolation && params) { Object.keys(params).forEach(param => { const value = params[param]; result = result.replace(new RegExp(`{{${param}}}`, 'g'), String(value)); }); } return result; } // フォールバック if (fallback) { return fallback; } // デフォルトフォールバック return key; }, [weblateT, translations, namespace]); // 翻訳項目を更新 const updateTranslation = useCallback(async (key, value, options) => { if (!translatorClient) { throw new Error('Translator client not initialized'); } const targetLanguage = options?.language || currentLanguage; const translation = translations.find(item => item.key === key || item.key.endsWith(`.${key}`)); if (!translation) { throw new Error(`Translation not found for key: ${key}`); } try { const updated = await translatorClient.updateTranslation(translation.id, value, targetLanguage, { state: options?.state, }); // ローカルの翻訳データを更新 setTranslations(prev => prev.map(item => item.id === translation.id ? { ...item, ...updated } : item)); log.info(`Translation updated: ${key} = ${value}`); return updated; } catch (error) { log.error(`Failed to update translation: ${error}`); throw error; } }, [translatorClient, currentLanguage, translations]); // 翻訳を検索 const searchTranslations = useCallback(async (query, options) => { if (!translatorClient) { throw new Error('Translator client not initialized'); } const targetLanguage = options?.language || currentLanguage; try { const result = await translatorClient.searchTranslations(query, { language: targetLanguage, limit: options?.limit, }); return result; } catch (error) { log.error(`Failed to search translations: ${error}`); throw error; } }, [translatorClient, currentLanguage]); // 言語を変更 const changeLanguage = useCallback(async (lang) => { setCurrentLanguage(lang); setWeblateLanguage(lang); if (translatorClient) { await fetchTranslations(lang); await fetchStats(lang); } }, [translatorClient, fetchTranslations, fetchStats, setWeblateLanguage]); // 利用可能な言語を取得 const supportedLanguages = useMemo(() => { if (!translatorClient) return []; // 設定から取得 const config = translatorConfig.fromEnv(); return config.supportedLanguages; }, [translatorClient]); // 翻訳の進捗率を計算 const progress = useMemo(() => { if (!stats) return 0; return stats.translated_percent || 0; }, [stats]); // 未翻訳の項目数を取得 const untranslatedCount = useMemo(() => { if (!stats) return 0; return stats.total - stats.translated; }, [stats]); // 翻訳が必要な項目をフィルタ const pendingTranslations = useMemo(() => { return translations.filter(item => !item.translated || item.fuzzy || item.state === 'needs-editing'); }, [translations]); // 権限チェック const canEdit = useMemo(() => { if (!translatorClient || !user) return false; try { return translatorClient.checkTranslationPermission('write'); } catch (error) { return false; } }, [translatorClient, user]); const canManage = useMemo(() => { if (!translatorClient || !user) return false; try { return translatorClient.checkTranslationPermission('manage'); } catch (error) { return false; } }, [translatorClient, user]); // デバッグ情報 const debugInfo = useMemo(() => ({ initialized, translatorClient: !!translatorClient, currentLanguage, translationsCount: translations.length, stats, supportedLanguages, canEdit, canManage, loading, error, }), [ initialized, translatorClient, currentLanguage, translations.length, stats, supportedLanguages, canEdit, canManage, loading, error, ]); return { // 翻訳機能 t, updateTranslation, searchTranslations, // 言語管理 currentLanguage, changeLanguage, supportedLanguages, // データ translations, pendingTranslations, stats, progress, untranslatedCount, // 状態 loading, error, initialized, // 権限 canEdit, canManage, // 操作 refresh: fetchTranslations, refreshStats: fetchStats, // クライアント client: translatorClient, // デバッグ debugInfo, }; } /** * 翻訳管理用フック(管理者向け) */ export function useTranslatorAdmin() { const { client: translatorClient, canManage } = useTranslator(); const [projects, setProjects] = useState([]); const [components, setComponents] = useState([]); const [languages, setLanguages] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // プロジェクト一覧を取得 const fetchProjects = useCallback(async () => { if (!translatorClient || !canManage) return; setLoading(true); setError(null); try { const projectList = await translatorClient.getProjects(); setProjects(projectList); } catch (error) { log.error(`Failed to fetch projects: ${error}`); setError(String(error)); } finally { setLoading(false); } }, [translatorClient, canManage]); // コンポーネント一覧を取得 const fetchComponents = useCallback(async (projectSlug) => { if (!translatorClient || !canManage) return; setLoading(true); setError(null); try { const componentList = await translatorClient.getComponents(projectSlug); setComponents(componentList); } catch (error) { log.error(`Failed to fetch components: ${error}`); setError(String(error)); } finally { setLoading(false); } }, [translatorClient, canManage]); // 言語一覧を取得 const fetchLanguages = useCallback(async (projectSlug) => { if (!translatorClient || !canManage) return; setLoading(true); setError(null); try { const languageList = await translatorClient.getLanguages(projectSlug); setLanguages(languageList); } catch (error) { log.error(`Failed to fetch languages: ${error}`); setError(String(error)); } finally { setLoading(false); } }, [translatorClient, canManage]); // プロジェクト作成 const createProject = useCallback(async (project) => { if (!translatorClient || !canManage) { throw new Error('Permission denied'); } try { const created = await translatorClient.createProject(project); await fetchProjects(); // リフレッシュ return created; } catch (error) { log.error(`Failed to create project: ${error}`); throw error; } }, [translatorClient, canManage, fetchProjects]); // コンポーネント作成 const createComponent = useCallback(async (projectSlug, component) => { if (!translatorClient || !canManage) { throw new Error('Permission denied'); } try { const created = await translatorClient.createComponent(projectSlug, component); await fetchComponents(projectSlug); // リフレッシュ return created; } catch (error) { log.error(`Failed to create component: ${error}`); throw error; } }, [translatorClient, canManage, fetchComponents]); // テナント用プロジェクト初期化 const initializeTenantProject = useCallback(async (tenantId) => { if (!translatorClient || !canManage) { throw new Error('Permission denied'); } try { const project = await translatorClient.initializeTenantProject(tenantId); await fetchProjects(); // リフレッシュ return project; } catch (error) { log.error(`Failed to initialize tenant project: ${error}`); throw error; } }, [translatorClient, canManage, fetchProjects]); useEffect(() => { if (translatorClient && canManage) { fetchProjects(); } }, [translatorClient, canManage, fetchProjects]); return { // データ projects, components, languages, // 状態 loading, error, canManage, // 操作 fetchProjects, fetchComponents, fetchLanguages, createProject, createComponent, initializeTenantProject, // クライアント client: translatorClient, }; } /** * 翻訳統計用フック */ export function useTranslationStats(language) { const { client: translatorClient, currentLanguage } = useTranslator(); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const targetLanguage = language || currentLanguage; const fetchStats = useCallback(async () => { if (!translatorClient) return; setLoading(true); setError(null); try { const translationStats = await translatorClient.getTranslationStats(targetLanguage); setStats(translationStats); } catch (error) { log.error(`Failed to fetch translation stats: ${error}`); setError(String(error)); } finally { setLoading(false); } }, [translatorClient, targetLanguage]); useEffect(() => { if (translatorClient) { fetchStats(); } }, [translatorClient, fetchStats]); const progressInfo = useMemo(() => { if (!stats) return null; return { total: stats.total, translated: stats.translated, approved: stats.approved, fuzzy: stats.fuzzy, pending: stats.pending, translatedPercent: stats.translated_percent, approvedPercent: stats.approved_percent, fuzzyPercent: stats.fuzzy_percent, pendingPercent: stats.pending_percent, wordsTotal: stats.total_words, wordsTranslated: stats.translated_words, wordsApproved: stats.approved_words, wordsTranslatedPercent: stats.translated_words_percent, wordsApprovedPercent: stats.approved_words_percent, lastChange: stats.last_change, lastAuthor: stats.last_author, }; }, [stats]); return { stats, progressInfo, loading, error, refresh: fetchStats, language: targetLanguage, }; } //# sourceMappingURL=useTranslator.js.map