From 00df1a044ae53690276a11ae5bc0ba6a00bb670d Mon Sep 17 00:00:00 2001 From: BharathKShetty Date: Thu, 14 Aug 2025 21:40:53 +0530 Subject: [PATCH 1/2] =?UTF-8?q?Refactor=20Translation=20Builder=20?= =?UTF-8?q?=E2=80=94=20preserve=20edits=20&=20sync=20keys=20incrementally?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../translation-builder.component.tsx | 82 +++++++++++++++++-- 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/src/components/translation-builder/translation-builder.component.tsx b/src/components/translation-builder/translation-builder.component.tsx index 198857a2a..836f033ef 100644 --- a/src/components/translation-builder/translation-builder.component.tsx +++ b/src/components/translation-builder/translation-builder.component.tsx @@ -19,6 +19,7 @@ interface TranslationBuilderProps { const TranslationBuilder: React.FC = ({ formSchema, onUpdateSchema }) => { const { t } = useTranslation(); const languageOptions = useLanguageOptions(); + const [localTranslations, setLocalTranslations] = useState>>({}); const [selectedLanguageCode, setSelectedLanguageCode] = useState(() => languageOptions[0]?.code ?? 'en'); const [translations, setTranslations] = useState>({}); const [loading, setLoading] = useState(false); @@ -29,6 +30,7 @@ const TranslationBuilder: React.FC = ({ formSchema, onU const [searchQuery, setSearchQuery] = useState(''); const [debouncedQuery, setDebouncedQuery] = useState(searchQuery); const { formUuid } = useParams<{ formUuid?: string }>(); + useEffect(() => { const handler = setTimeout(() => { setDebouncedQuery(searchQuery); @@ -42,10 +44,46 @@ const TranslationBuilder: React.FC = ({ formSchema, onU return formSchema ? extractTranslatableStrings(formSchema) : {}; }, [formSchema]); + useEffect(() => { + if (!formSchema) return; + + const fallback = fallbackStrings; + const current = translations; + + const updated: Record = {}; + Object.keys(fallback).forEach((key) => { + updated[key] = current[key] ?? fallback[key] ?? ''; + }); + + const changed = + Object.keys(updated).length !== Object.keys(current).length || + Object.keys(updated).some((k) => updated[k] !== current[k]); + + if (changed) { + setTranslations(updated); + setLocalTranslations((prev) => ({ + ...prev, + [selectedLanguageCode]: updated, + })); + onUpdateSchema({ + ...formSchema, + translations: { + ...(formSchema.translations || {}), + [selectedLanguageCode]: updated, + }, + }); + } + }, [formSchema, fallbackStrings, selectedLanguageCode, onUpdateSchema, translations]); + const handleUpdateValue = useCallback( (key: string, newValue: string) => { const updatedTranslations = { ...translations, [key]: newValue }; setTranslations(updatedTranslations); + setLocalTranslations((prev) => ({ + ...prev, + [langCode]: updatedTranslations, + })); + if (formSchema) { const updatedSchema = { ...formSchema }; if (!updatedSchema.translations) { @@ -108,22 +146,49 @@ const TranslationBuilder: React.FC = ({ formSchema, onU [fallbackStrings], ); - useEffect(() => { - if (selectedLanguageCode === 'en' && formSchema) { - setTranslations(fallbackStrings); - } - }, [selectedLanguageCode, formSchema, fallbackStrings]); + function mergeTranslations( + local: Record = {}, + backend: Record = {}, + fallback: Record = {}, + ): Record { + const merged: Record = {}; + const allKeys = new Set([...Object.keys(fallback), ...Object.keys(backend), ...Object.keys(local)]); + allKeys.forEach((key) => { + if (local[key] !== undefined) { + merged[key] = local[key]; + } else if (backend[key] !== undefined) { + merged[key] = backend[key]; + } else { + merged[key] = fallback[key] ?? ''; + } + }); + return merged; + } const languageChanger = async (newLangCode: string) => { setSelectedLanguageCode(newLangCode); - if (!formSchema || newLangCode === 'en') { - setTranslations(fallbackStrings); + + if (!formSchema) { + setTranslations({}); + return; + } + if (localTranslations[newLangCode]) { + setTranslations(localTranslations[newLangCode]); return; } setLoading(true); try { - const merged = await fetchBackendTranslations(formUuid, newLangCode, fallbackStrings); + let backend = await fetchBackendTranslations(formUuid, newLangCode, fallbackStrings); + let schemaTranslations = formSchema.translations?.[newLangCode] || {}; + + const merged = mergeTranslations(schemaTranslations, backend || {}, fallbackStrings); + + setLocalTranslations((prev) => ({ + ...prev, + [newLangCode]: merged, + })); + setTranslations(merged); const updatedSchema = { @@ -133,7 +198,6 @@ const TranslationBuilder: React.FC = ({ formSchema, onU [newLangCode]: merged, }, }; - onUpdateSchema(updatedSchema); } catch (err) { setError('Failed to load backend translations.'); From 30ceb82d124ff5cb0ba6f1ea782895e1191ec09a Mon Sep 17 00:00:00 2001 From: BharathKShetty Date: Wed, 27 Aug 2025 23:10:38 +0530 Subject: [PATCH 2/2] Fixed the failing e2e tests --- .../translation-builder.component.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/components/translation-builder/translation-builder.component.tsx b/src/components/translation-builder/translation-builder.component.tsx index 836f033ef..1e7fe3ef3 100644 --- a/src/components/translation-builder/translation-builder.component.tsx +++ b/src/components/translation-builder/translation-builder.component.tsx @@ -65,15 +65,11 @@ const TranslationBuilder: React.FC = ({ formSchema, onU ...prev, [selectedLanguageCode]: updated, })); - onUpdateSchema({ - ...formSchema, - translations: { - ...(formSchema.translations || {}), - [selectedLanguageCode]: updated, - }, - }); + + // Removed the onUpdateSchema call from here + // The schema should only be updated on user-initiated actions } - }, [formSchema, fallbackStrings, selectedLanguageCode, onUpdateSchema, translations]); + }, [formSchema, fallbackStrings, selectedLanguageCode, translations]); const handleUpdateValue = useCallback( (key: string, newValue: string) => {