55 lines
2.3 KiB
TypeScript
55 lines
2.3 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { useTempoUi } from '@/components/tempo-brand';
|
|
import { translateText } from '@/translation/service';
|
|
import { protectTranslationTokens, restoreTranslationTokens } from '@/translation/utils';
|
|
|
|
// Only the fixed public institutional texts using this component are sent to Tempo.
|
|
const cache = new Map<string, string>();
|
|
const pending = new Map<string, Promise<string>>();
|
|
|
|
function translateInstitutionalText(text: string, language: string): Promise<string> {
|
|
const key = JSON.stringify([language, text]);
|
|
const cached = cache.get(key);
|
|
if (cached) return Promise.resolve(cached);
|
|
const existing = pending.get(key);
|
|
if (existing) return existing;
|
|
|
|
const protectedText = protectTranslationTokens(text);
|
|
const request = translateText({
|
|
text: protectedText.text,
|
|
sourceLanguage: 'pt',
|
|
targetLanguage: language,
|
|
}).then(({ translatedText }) => {
|
|
// Do not accept a response that lost an identifier, URL or protected term.
|
|
for (const token of protectedText.replacements.keys()) {
|
|
if (!translatedText.includes(token)) throw new Error('Translation lost a protected token');
|
|
}
|
|
const result = restoreTranslationTokens(translatedText, protectedText.replacements);
|
|
// Tempo's API returns the input on upstream failure: do not cache it as success.
|
|
if (result !== text) cache.set(key, result);
|
|
return result;
|
|
}).finally(() => pending.delete(key));
|
|
pending.set(key, request);
|
|
return request;
|
|
}
|
|
|
|
export function Trans({ children }: { children: string }) {
|
|
const { language, t } = useTempoUi();
|
|
const localText = t(children);
|
|
const [result, setResult] = useState<{ key: string; text: string } | null>(null);
|
|
const key = JSON.stringify([language, children]);
|
|
|
|
useEffect(() => {
|
|
if (language === 'pt' || localText !== children || !children.trim()) return;
|
|
let active = true;
|
|
translateInstitutionalText(children, language).then((text) => {
|
|
if (active) setResult({ key, text });
|
|
}).catch(() => {
|
|
if (active) setResult(null);
|
|
});
|
|
return () => { active = false; };
|
|
}, [children, key, language, localText]);
|
|
|
|
return <span data-no-translate>{result?.key === key ? result.text : localText}</span>;
|
|
}
|