mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 15:36:18 +00:00
WIP preserve local changes before duckie merge
This commit is contained in:
@@ -0,0 +1,589 @@
|
||||
import { GetConfiguration, GetLocalizationManager, GetSessionDataManager, TranslationLanguagesEvent, TranslationLanguagesRequestComposer, TranslationResultEvent, TranslationTextRequestComposer } from '@nitrots/nitro-renderer';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useBetween } from 'use-between';
|
||||
import { LocalStorageKeys, SendMessageComposer } from '../../api';
|
||||
import { useMessageEvent } from '../events';
|
||||
import { useLocalStorage } from '../useLocalStorage';
|
||||
|
||||
const REQUEST_TIMEOUT_MS = 8000;
|
||||
const OUTGOING_QUEUE_TTL_MS = 30000;
|
||||
|
||||
export interface ITranslationSettings
|
||||
{
|
||||
enabled: boolean;
|
||||
incomingTargetLanguage: string;
|
||||
outgoingTargetLanguage: string;
|
||||
uiTextLanguage: string;
|
||||
}
|
||||
|
||||
export interface ITranslationLanguage
|
||||
{
|
||||
code: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ITranslationTextLocale extends ITranslationLanguage
|
||||
{
|
||||
file: string;
|
||||
}
|
||||
|
||||
export interface IResolvedTranslation
|
||||
{
|
||||
originalText: string;
|
||||
translatedText: string;
|
||||
detectedLanguage: string;
|
||||
targetLanguage: string;
|
||||
}
|
||||
|
||||
interface IPendingTranslationRequest
|
||||
{
|
||||
resolve: (translation: IResolvedTranslation) => void;
|
||||
reject: (error: Error) => void;
|
||||
timeoutId: number;
|
||||
}
|
||||
|
||||
interface IQueuedOutgoingTranslation extends IResolvedTranslation
|
||||
{
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
const normalizeLanguageCode = (value: string) =>
|
||||
{
|
||||
if(!value || !value.trim().length) return '';
|
||||
|
||||
const normalized = value.trim().replace('_', '-');
|
||||
const parts = normalized.split('-');
|
||||
|
||||
if(parts.length === 1) return parts[0].toLowerCase();
|
||||
|
||||
return `${ parts[0].toLowerCase() }-${ parts[1].toUpperCase() }`;
|
||||
};
|
||||
|
||||
const TEXT_TRANSLATION_LOCALES: ITranslationTextLocale[] = [
|
||||
{ code: 'pt-BR', name: 'Portuguese (Brazil)', file: 'br' },
|
||||
{ code: 'en', name: 'English', file: 'com' },
|
||||
{ code: 'de', name: 'German', file: 'de' },
|
||||
{ code: 'es', name: 'Spanish', file: 'es' },
|
||||
{ code: 'fi', name: 'Finnish', file: 'fi' },
|
||||
{ code: 'fr', name: 'French', file: 'fr' },
|
||||
{ code: 'it', name: 'Italian', file: 'it' },
|
||||
{ code: 'nl', name: 'Dutch', file: 'nl' },
|
||||
{ code: 'tr', name: 'Turkish', file: 'tr' }
|
||||
];
|
||||
|
||||
const resolveTextTranslationLocale = (value: string) =>
|
||||
{
|
||||
const normalizedValue = normalizeLanguageCode(value);
|
||||
|
||||
if(!normalizedValue.length) return null;
|
||||
|
||||
const exactMatch = TEXT_TRANSLATION_LOCALES.find(locale => (normalizeLanguageCode(locale.code) === normalizedValue));
|
||||
|
||||
if(exactMatch) return exactMatch;
|
||||
|
||||
const normalizedBase = normalizedValue.split('-')[0];
|
||||
|
||||
if(normalizedBase === 'pt') return TEXT_TRANSLATION_LOCALES.find(locale => (locale.file === 'br')) || null;
|
||||
|
||||
return TEXT_TRANSLATION_LOCALES.find(locale => (normalizeLanguageCode(locale.code).split('-')[0] === normalizedBase)) || null;
|
||||
};
|
||||
|
||||
const interpolateTranslationUrl = (template: string, file: string) =>
|
||||
{
|
||||
if(!template || !template.length) return '';
|
||||
|
||||
return GetConfiguration().interpolate(
|
||||
template
|
||||
.replace(/%locale%/gi, file)
|
||||
.replace(/%timestamp%/gi, Date.now().toString()));
|
||||
};
|
||||
|
||||
const getTextTranslationUrl = (file: string) =>
|
||||
{
|
||||
const configuredTranslationUrl = GetConfiguration().getValue<string>('external.texts.translation.url') || '';
|
||||
|
||||
if(configuredTranslationUrl.length)
|
||||
{
|
||||
return interpolateTranslationUrl(configuredTranslationUrl, file);
|
||||
}
|
||||
|
||||
const externalTextUrls = GetConfiguration().getValue<string[]>('external.texts.url') || [];
|
||||
const externalTextsUrl = externalTextUrls.length ? GetConfiguration().interpolate(externalTextUrls[0]) : '';
|
||||
|
||||
if(!externalTextsUrl.length) return `/text_translate/ExternalTexts_${ file }.json`;
|
||||
|
||||
const lastSlashIndex = externalTextsUrl.lastIndexOf('/');
|
||||
|
||||
if(lastSlashIndex === -1) return `text_translate/ExternalTexts_${ file }.json`;
|
||||
|
||||
const basePath = externalTextsUrl.substring(0, lastSlashIndex);
|
||||
|
||||
return `${ basePath }/text_translate/ExternalTexts_${ file }.json`;
|
||||
};
|
||||
|
||||
const getFurnitureTranslationUrl = (file: string) =>
|
||||
{
|
||||
const configuredTranslationUrl = GetConfiguration().getValue<string>('furnidata.translation.url') || '';
|
||||
|
||||
if(configuredTranslationUrl.length)
|
||||
{
|
||||
return interpolateTranslationUrl(configuredTranslationUrl, file);
|
||||
}
|
||||
|
||||
const furnidataUrl = GetConfiguration().interpolate(GetConfiguration().getValue<string>('furnidata.url') || '');
|
||||
|
||||
if(!furnidataUrl.length) return `/furniture_translate/FurnitureData_${ file }.json`;
|
||||
|
||||
const lastSlashIndex = furnidataUrl.lastIndexOf('/');
|
||||
|
||||
if(lastSlashIndex === -1) return `furniture_translate/FurnitureData_${ file }.json`;
|
||||
|
||||
const basePath = furnidataUrl.substring(0, lastSlashIndex);
|
||||
|
||||
return `${ basePath }/furniture_translate/FurnitureData_${ file }.json`;
|
||||
};
|
||||
|
||||
const dispatchLocalizationUpdated = () =>
|
||||
{
|
||||
if(typeof window === 'undefined') return;
|
||||
|
||||
window.dispatchEvent(new CustomEvent('nitro-localization-updated'));
|
||||
};
|
||||
|
||||
const getBrowserLanguageCode = () =>
|
||||
{
|
||||
if(typeof navigator === 'undefined') return 'en';
|
||||
|
||||
return normalizeLanguageCode(navigator.language || 'en').split('-')[0] || 'en';
|
||||
};
|
||||
|
||||
const decodeHtmlEntities = (value: string) =>
|
||||
{
|
||||
if(!value || (typeof window === 'undefined')) return value;
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
|
||||
textarea.innerHTML = value;
|
||||
|
||||
return textarea.value;
|
||||
};
|
||||
|
||||
const resolveSupportedLanguage = (value: string, languages: ITranslationLanguage[]) =>
|
||||
{
|
||||
const normalizedValue = normalizeLanguageCode(value);
|
||||
|
||||
if(!languages.length) return normalizedValue || 'en';
|
||||
|
||||
const exactMatch = languages.find(language => (normalizeLanguageCode(language.code) === normalizedValue));
|
||||
|
||||
if(exactMatch) return exactMatch.code;
|
||||
|
||||
const normalizedBase = normalizedValue.split('-')[0];
|
||||
const baseMatch = languages.find(language => (normalizeLanguageCode(language.code).split('-')[0] === normalizedBase));
|
||||
|
||||
if(baseMatch) return baseMatch.code;
|
||||
|
||||
const englishMatch = languages.find(language => (normalizeLanguageCode(language.code).split('-')[0] === 'en'));
|
||||
|
||||
if(englishMatch) return englishMatch.code;
|
||||
|
||||
return languages[0].code;
|
||||
};
|
||||
|
||||
const useTranslationState = () =>
|
||||
{
|
||||
const defaultTargetLanguage = getBrowserLanguageCode();
|
||||
const [ settings, setSettings ] = useLocalStorage<ITranslationSettings>(LocalStorageKeys.CHAT_TRANSLATION_SETTINGS, {
|
||||
enabled: false,
|
||||
incomingTargetLanguage: defaultTargetLanguage,
|
||||
outgoingTargetLanguage: defaultTargetLanguage,
|
||||
uiTextLanguage: ''
|
||||
});
|
||||
const [ supportedLanguages, setSupportedLanguages ] = useState<ITranslationLanguage[]>([]);
|
||||
const [ availableTextLocales ] = useState<ITranslationTextLocale[]>(TEXT_TRANSLATION_LOCALES);
|
||||
const [ languagesLoading, setLanguagesLoading ] = useState(false);
|
||||
const [ languagesLoaded, setLanguagesLoaded ] = useState(false);
|
||||
const [ localizationTextsLoading, setLocalizationTextsLoading ] = useState(false);
|
||||
const [ lastIncomingLanguage, setLastIncomingLanguage ] = useState('');
|
||||
const [ lastOutgoingLanguage, setLastOutgoingLanguage ] = useState('');
|
||||
const [ lastError, setLastError ] = useState('');
|
||||
const requestIdRef = useRef(0);
|
||||
const languagesTimeoutRef = useRef(0);
|
||||
const pendingRequestsRef = useRef(new Map<number, IPendingTranslationRequest>());
|
||||
const translationCacheRef = useRef(new Map<string, IResolvedTranslation>());
|
||||
const outgoingQueueRef = useRef(new Map<string, IQueuedOutgoingTranslation[]>());
|
||||
const localizationRequestRef = useRef(0);
|
||||
|
||||
const clearLanguagesTimeout = useCallback(() =>
|
||||
{
|
||||
if(!languagesTimeoutRef.current) return;
|
||||
|
||||
window.clearTimeout(languagesTimeoutRef.current);
|
||||
languagesTimeoutRef.current = 0;
|
||||
}, []);
|
||||
|
||||
const pruneOutgoingQueue = useCallback(() =>
|
||||
{
|
||||
const now = Date.now();
|
||||
|
||||
outgoingQueueRef.current.forEach((entries, key) =>
|
||||
{
|
||||
const activeEntries = entries.filter(entry => (entry.expiresAt > now));
|
||||
|
||||
if(activeEntries.length)
|
||||
{
|
||||
outgoingQueueRef.current.set(key, activeEntries);
|
||||
return;
|
||||
}
|
||||
|
||||
outgoingQueueRef.current.delete(key);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const updateSettings = useCallback((partial: Partial<ITranslationSettings>) =>
|
||||
{
|
||||
setSettings(prevValue => ({ ...prevValue, ...partial }));
|
||||
}, [ setSettings ]);
|
||||
|
||||
const getLanguageName = useCallback((languageCode: string) =>
|
||||
{
|
||||
const normalizedLanguageCode = normalizeLanguageCode(languageCode);
|
||||
|
||||
if(!normalizedLanguageCode.length) return 'auto';
|
||||
|
||||
const exactMatch = supportedLanguages.find(language => (normalizeLanguageCode(language.code) === normalizedLanguageCode));
|
||||
|
||||
if(exactMatch) return exactMatch.name;
|
||||
|
||||
const normalizedBase = normalizedLanguageCode.split('-')[0];
|
||||
const baseMatch = supportedLanguages.find(language => (normalizeLanguageCode(language.code).split('-')[0] === normalizedBase));
|
||||
|
||||
return baseMatch?.name || normalizedLanguageCode;
|
||||
}, [ supportedLanguages ]);
|
||||
|
||||
const handleLanguagesEvent = useCallback((event: TranslationLanguagesEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
clearLanguagesTimeout();
|
||||
setLanguagesLoading(false);
|
||||
|
||||
if(!parser.success)
|
||||
{
|
||||
setLanguagesLoaded(false);
|
||||
setLastError(parser.errorMessage || 'Unable to load Google Translate languages.');
|
||||
return;
|
||||
}
|
||||
|
||||
const nextLanguages = parser.languages.map(language => ({
|
||||
code: normalizeLanguageCode(language.code),
|
||||
name: language.name
|
||||
}));
|
||||
|
||||
setSupportedLanguages(nextLanguages);
|
||||
setLanguagesLoaded(true);
|
||||
setLastError('');
|
||||
}, [ clearLanguagesTimeout ]);
|
||||
|
||||
const handleTranslationResult = useCallback((event: TranslationResultEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
const pendingRequest = pendingRequestsRef.current.get(parser.requestId);
|
||||
|
||||
if(!pendingRequest) return;
|
||||
|
||||
window.clearTimeout(pendingRequest.timeoutId);
|
||||
pendingRequestsRef.current.delete(parser.requestId);
|
||||
|
||||
if(!parser.success)
|
||||
{
|
||||
pendingRequest.reject(new Error(parser.errorMessage || 'Unable to translate text.'));
|
||||
return;
|
||||
}
|
||||
|
||||
pendingRequest.resolve({
|
||||
originalText: decodeHtmlEntities(parser.originalText || ''),
|
||||
translatedText: decodeHtmlEntities(parser.translatedText || ''),
|
||||
detectedLanguage: normalizeLanguageCode(parser.detectedLanguage || ''),
|
||||
targetLanguage: normalizeLanguageCode(parser.targetLanguage || '')
|
||||
});
|
||||
}, []);
|
||||
|
||||
useMessageEvent<TranslationLanguagesEvent>(TranslationLanguagesEvent, handleLanguagesEvent);
|
||||
useMessageEvent<TranslationResultEvent>(TranslationResultEvent, handleTranslationResult);
|
||||
|
||||
const ensureSupportedLanguagesLoaded = useCallback((force: boolean = false) =>
|
||||
{
|
||||
if(languagesLoading) return;
|
||||
if(languagesLoaded && !force) return;
|
||||
|
||||
setLanguagesLoading(true);
|
||||
setLastError('');
|
||||
clearLanguagesTimeout();
|
||||
|
||||
languagesTimeoutRef.current = window.setTimeout(() =>
|
||||
{
|
||||
setLanguagesLoading(false);
|
||||
setLastError('Google Translate did not respond while loading languages.');
|
||||
}, REQUEST_TIMEOUT_MS);
|
||||
|
||||
SendMessageComposer(new TranslationLanguagesRequestComposer(getBrowserLanguageCode()));
|
||||
}, [ clearLanguagesTimeout, languagesLoaded, languagesLoading ]);
|
||||
|
||||
const translateText = useCallback((text: string, targetLanguage: string) =>
|
||||
{
|
||||
const safeText = (text || '');
|
||||
const normalizedTargetLanguage = normalizeLanguageCode(targetLanguage || defaultTargetLanguage) || defaultTargetLanguage;
|
||||
|
||||
if(!safeText.trim().length)
|
||||
{
|
||||
return Promise.resolve({
|
||||
originalText: safeText,
|
||||
translatedText: safeText,
|
||||
detectedLanguage: '',
|
||||
targetLanguage: normalizedTargetLanguage
|
||||
});
|
||||
}
|
||||
|
||||
const cacheKey = `${ normalizedTargetLanguage }\u0000${ safeText }`;
|
||||
const cachedValue = translationCacheRef.current.get(cacheKey);
|
||||
|
||||
if(cachedValue) return Promise.resolve(cachedValue);
|
||||
|
||||
return new Promise<IResolvedTranslation>((resolve, reject) =>
|
||||
{
|
||||
const requestId = ++requestIdRef.current;
|
||||
const timeoutId = window.setTimeout(() =>
|
||||
{
|
||||
pendingRequestsRef.current.delete(requestId);
|
||||
reject(new Error('Google Translate did not respond in time.'));
|
||||
}, REQUEST_TIMEOUT_MS);
|
||||
|
||||
pendingRequestsRef.current.set(requestId, { resolve, reject, timeoutId });
|
||||
SendMessageComposer(new TranslationTextRequestComposer(requestId, safeText, normalizedTargetLanguage));
|
||||
}).then(result =>
|
||||
{
|
||||
translationCacheRef.current.set(cacheKey, result);
|
||||
|
||||
return result;
|
||||
});
|
||||
}, [ defaultTargetLanguage ]);
|
||||
|
||||
const translateIncoming = useCallback(async (text: string) =>
|
||||
{
|
||||
if(!settings.enabled) return null;
|
||||
|
||||
try
|
||||
{
|
||||
const result = await translateText(text, settings.incomingTargetLanguage || defaultTargetLanguage);
|
||||
|
||||
setLastIncomingLanguage(result.detectedLanguage || '');
|
||||
setLastError('');
|
||||
|
||||
return result;
|
||||
}
|
||||
catch(error)
|
||||
{
|
||||
setLastError((error as Error)?.message || 'Unable to translate incoming text.');
|
||||
|
||||
return null;
|
||||
}
|
||||
}, [ defaultTargetLanguage, settings.enabled, settings.incomingTargetLanguage, translateText ]);
|
||||
|
||||
const translateOutgoing = useCallback(async (text: string) =>
|
||||
{
|
||||
if(!settings.enabled) return null;
|
||||
|
||||
try
|
||||
{
|
||||
const result = await translateText(text, settings.outgoingTargetLanguage || defaultTargetLanguage);
|
||||
|
||||
setLastOutgoingLanguage(result.detectedLanguage || '');
|
||||
setLastError('');
|
||||
|
||||
return result;
|
||||
}
|
||||
catch(error)
|
||||
{
|
||||
setLastError((error as Error)?.message || 'Unable to translate outgoing text.');
|
||||
|
||||
return null;
|
||||
}
|
||||
}, [ defaultTargetLanguage, settings.enabled, settings.outgoingTargetLanguage, translateText ]);
|
||||
|
||||
const enqueueOutgoingTranslation = useCallback((translation: IResolvedTranslation) =>
|
||||
{
|
||||
if(!translation) return;
|
||||
|
||||
pruneOutgoingQueue();
|
||||
|
||||
const queueKey = translation.translatedText || translation.originalText;
|
||||
const currentEntries = outgoingQueueRef.current.get(queueKey) || [];
|
||||
|
||||
currentEntries.push({
|
||||
...translation,
|
||||
expiresAt: (Date.now() + OUTGOING_QUEUE_TTL_MS)
|
||||
});
|
||||
|
||||
outgoingQueueRef.current.set(queueKey, currentEntries);
|
||||
setLastOutgoingLanguage(translation.detectedLanguage || '');
|
||||
}, [ pruneOutgoingQueue ]);
|
||||
|
||||
const consumeOutgoingTranslation = useCallback((translatedText: string) =>
|
||||
{
|
||||
pruneOutgoingQueue();
|
||||
|
||||
const queueKey = translatedText || '';
|
||||
const currentEntries = outgoingQueueRef.current.get(queueKey);
|
||||
|
||||
if(!currentEntries?.length) return null;
|
||||
|
||||
const entry = currentEntries.shift();
|
||||
|
||||
if(currentEntries.length) outgoingQueueRef.current.set(queueKey, currentEntries);
|
||||
else outgoingQueueRef.current.delete(queueKey);
|
||||
|
||||
if(entry?.detectedLanguage) setLastOutgoingLanguage(entry.detectedLanguage);
|
||||
|
||||
return entry || null;
|
||||
}, [ pruneOutgoingQueue ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!settings.enabled) return;
|
||||
|
||||
ensureSupportedLanguagesLoaded();
|
||||
}, [ ensureSupportedLanguagesLoaded, settings.enabled ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!supportedLanguages.length) return;
|
||||
|
||||
const resolvedIncomingTargetLanguage = resolveSupportedLanguage(settings.incomingTargetLanguage || defaultTargetLanguage, supportedLanguages);
|
||||
const resolvedOutgoingTargetLanguage = resolveSupportedLanguage(settings.outgoingTargetLanguage || defaultTargetLanguage, supportedLanguages);
|
||||
|
||||
if((resolvedIncomingTargetLanguage === settings.incomingTargetLanguage) && (resolvedOutgoingTargetLanguage === settings.outgoingTargetLanguage)) return;
|
||||
|
||||
setSettings(prevValue => ({
|
||||
...prevValue,
|
||||
incomingTargetLanguage: resolvedIncomingTargetLanguage,
|
||||
outgoingTargetLanguage: resolvedOutgoingTargetLanguage
|
||||
}));
|
||||
}, [ defaultTargetLanguage, setSettings, settings.incomingTargetLanguage, settings.outgoingTargetLanguage, supportedLanguages ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
let disposed = false;
|
||||
const requestId = ++localizationRequestRef.current;
|
||||
const localizationManager = GetLocalizationManager();
|
||||
const sessionDataManager = GetSessionDataManager();
|
||||
const selectedLocale = resolveTextTranslationLocale(settings.uiTextLanguage || '');
|
||||
|
||||
const applyLocalizationOverride = async () =>
|
||||
{
|
||||
if(!selectedLocale)
|
||||
{
|
||||
localizationManager.clearOverrideValues();
|
||||
sessionDataManager.clearFurnitureDataOverrides();
|
||||
dispatchLocalizationUpdated();
|
||||
|
||||
if((localizationRequestRef.current === requestId) && !disposed)
|
||||
{
|
||||
setLocalizationTextsLoading(false);
|
||||
setLastError('');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(!disposed) setLocalizationTextsLoading(true);
|
||||
|
||||
try
|
||||
{
|
||||
const textUrl = getTextTranslationUrl(selectedLocale.file);
|
||||
const furnitureUrl = getFurnitureTranslationUrl(selectedLocale.file);
|
||||
const response = await fetch(textUrl);
|
||||
|
||||
if(response.status !== 200) throw new Error(`Unable to load ${ textUrl }`);
|
||||
|
||||
const data = await response.json();
|
||||
const overrideValues = new Map<string, string>();
|
||||
|
||||
Object.keys(data || {}).forEach(key => overrideValues.set(key, data[key]));
|
||||
|
||||
if(disposed || (localizationRequestRef.current !== requestId)) return;
|
||||
|
||||
localizationManager.setOverrideValues(overrideValues);
|
||||
|
||||
try
|
||||
{
|
||||
await sessionDataManager.applyFurnitureDataOverrides(furnitureUrl);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if(disposed || (localizationRequestRef.current !== requestId)) return;
|
||||
|
||||
sessionDataManager.clearFurnitureDataOverrides();
|
||||
}
|
||||
|
||||
dispatchLocalizationUpdated();
|
||||
setLastError('');
|
||||
}
|
||||
catch(error)
|
||||
{
|
||||
if(disposed || (localizationRequestRef.current !== requestId)) return;
|
||||
|
||||
localizationManager.clearOverrideValues();
|
||||
sessionDataManager.clearFurnitureDataOverrides();
|
||||
dispatchLocalizationUpdated();
|
||||
setLastError((error as Error)?.message || 'Unable to load translated UI texts.');
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(disposed || (localizationRequestRef.current !== requestId)) return;
|
||||
|
||||
setLocalizationTextsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
applyLocalizationOverride();
|
||||
|
||||
return () =>
|
||||
{
|
||||
disposed = true;
|
||||
};
|
||||
}, [ settings.uiTextLanguage ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
return () =>
|
||||
{
|
||||
clearLanguagesTimeout();
|
||||
|
||||
pendingRequestsRef.current.forEach(pendingRequest => window.clearTimeout(pendingRequest.timeoutId));
|
||||
pendingRequestsRef.current.clear();
|
||||
outgoingQueueRef.current.clear();
|
||||
};
|
||||
}, [ clearLanguagesTimeout ]);
|
||||
|
||||
return {
|
||||
settings,
|
||||
supportedLanguages,
|
||||
availableTextLocales,
|
||||
languagesLoading,
|
||||
languagesLoaded,
|
||||
localizationTextsLoading,
|
||||
lastIncomingLanguage,
|
||||
lastOutgoingLanguage,
|
||||
lastError,
|
||||
updateSettings,
|
||||
ensureSupportedLanguagesLoaded,
|
||||
translateIncoming,
|
||||
translateOutgoing,
|
||||
enqueueOutgoingTranslation,
|
||||
consumeOutgoingTranslation,
|
||||
getLanguageName
|
||||
};
|
||||
};
|
||||
|
||||
export const useTranslation = () => useBetween(useTranslationState);
|
||||
Reference in New Issue
Block a user