* chore: ensures relationship fields react to locale changes in the admin panel - fixes #1870 * chore: patches in default values for fields, and localized fields using fallbacks - fixes #1859 * chore: organizes field localization and sanitizing * Revert "Feat/1180 loading UI enhancements" * Feat/1180 loading UI enhancements * chore: safely sets tab if name field, only sets fallback value if it exists * chore: adds test to ensure text fields use fallback locale value when empty
This commit is contained in:
@@ -26,6 +26,7 @@ import { GetFilterOptions } from '../../../utilities/GetFilterOptions';
|
|||||||
import { SingleValue } from './select-components/SingleValue';
|
import { SingleValue } from './select-components/SingleValue';
|
||||||
import { MultiValueLabel } from './select-components/MultiValueLabel';
|
import { MultiValueLabel } from './select-components/MultiValueLabel';
|
||||||
import { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types';
|
import { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types';
|
||||||
|
import { useLocale } from '../../../utilities/Locale';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const { t, i18n } = useTranslation('fields');
|
const { t, i18n } = useTranslation('fields');
|
||||||
const { permissions } = useAuth();
|
const { permissions } = useAuth();
|
||||||
|
const locale = useLocale();
|
||||||
const formProcessing = useFormProcessing();
|
const formProcessing = useFormProcessing();
|
||||||
const hasMultipleRelations = Array.isArray(relationTo);
|
const hasMultipleRelations = Array.isArray(relationTo);
|
||||||
const [options, dispatchOptions] = useReducer(optionsReducer, []);
|
const [options, dispatchOptions] = useReducer(optionsReducer, []);
|
||||||
@@ -140,7 +142,7 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
limit: maxResultsPerRequest,
|
limit: maxResultsPerRequest,
|
||||||
page: lastLoadedPageToUse,
|
page: lastLoadedPageToUse,
|
||||||
sort: fieldToSearch,
|
sort: fieldToSearch,
|
||||||
locale: i18n.language,
|
locale,
|
||||||
depth: 0,
|
depth: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -203,6 +205,7 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
api,
|
api,
|
||||||
t,
|
t,
|
||||||
i18n,
|
i18n,
|
||||||
|
locale,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const updateSearch = useDebouncedCallback((searchArg: string, valueArg: unknown) => {
|
const updateSearch = useDebouncedCallback((searchArg: string, valueArg: unknown) => {
|
||||||
@@ -242,7 +245,7 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
depth: 0,
|
depth: 0,
|
||||||
locale: i18n.language,
|
locale,
|
||||||
limit: idsToLoad.length,
|
limit: idsToLoad.length,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -274,6 +277,7 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
api,
|
api,
|
||||||
i18n,
|
i18n,
|
||||||
relationTo,
|
relationTo,
|
||||||
|
locale,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Determine if we should switch to word boundary search
|
// Determine if we should switch to word boundary search
|
||||||
@@ -287,7 +291,7 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
setEnableWordBoundarySearch(!isIdOnly);
|
setEnableWordBoundarySearch(!isIdOnly);
|
||||||
}, [relationTo, collections]);
|
}, [relationTo, collections]);
|
||||||
|
|
||||||
// When relationTo or filterOptionsResult changes, reset component
|
// When (`relationTo` || `filterOptionsResult` || `locale`) changes, reset component
|
||||||
// Note - effect should not run on first run
|
// Note - effect should not run on first run
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (firstRun.current) {
|
if (firstRun.current) {
|
||||||
@@ -299,7 +303,7 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
setLastFullyLoadedRelation(-1);
|
setLastFullyLoadedRelation(-1);
|
||||||
setLastLoadedPage(1);
|
setLastLoadedPage(1);
|
||||||
setHasLoadedFirstPage(false);
|
setHasLoadedFirstPage(false);
|
||||||
}, [relationTo, filterOptionsResult]);
|
}, [relationTo, filterOptionsResult, locale]);
|
||||||
|
|
||||||
const onSave = useCallback<DocumentDrawerProps['onSave']>((args) => {
|
const onSave = useCallback<DocumentDrawerProps['onSave']>((args) => {
|
||||||
dispatchOptions({ type: 'UPDATE', doc: args.doc, collection: args.collectionConfig, i18n });
|
dispatchOptions({ type: 'UPDATE', doc: args.doc, collection: args.collectionConfig, i18n });
|
||||||
@@ -373,7 +377,7 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
sort: false,
|
sort: false,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
value={valueToRender}
|
value={valueToRender ?? null}
|
||||||
showError={showError}
|
showError={showError}
|
||||||
disabled={formProcessing}
|
disabled={formProcessing}
|
||||||
options={options}
|
options={options}
|
||||||
|
|||||||
@@ -46,22 +46,49 @@ export const promise = async ({
|
|||||||
delete siblingDoc[field.name];
|
delete siblingDoc[field.name];
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasLocalizedValue = flattenLocales
|
const shouldHoistLocalizedValue = flattenLocales
|
||||||
&& fieldAffectsData(field)
|
&& fieldAffectsData(field)
|
||||||
&& (typeof siblingDoc[field.name] === 'object' && siblingDoc[field.name] !== null)
|
&& (typeof siblingDoc[field.name] === 'object' && siblingDoc[field.name] !== null)
|
||||||
&& field.localized
|
&& field.localized
|
||||||
&& req.locale !== 'all';
|
&& req.locale !== 'all';
|
||||||
|
|
||||||
if (hasLocalizedValue) {
|
if (shouldHoistLocalizedValue) {
|
||||||
let localizedValue = siblingDoc[field.name][req.locale];
|
// replace actual value with localized value before sanitizing
|
||||||
if (typeof localizedValue === 'undefined' && req.fallbackLocale) localizedValue = siblingDoc[field.name][req.fallbackLocale];
|
// { [locale]: fields } -> fields
|
||||||
if (localizedValue === null && (field.type === 'array' || field.type === 'blocks')) localizedValue = siblingDoc[field.name][req.fallbackLocale];
|
const { locale } = req;
|
||||||
if (typeof localizedValue === 'undefined' && (field.type === 'group' || field.type === 'tab')) localizedValue = {};
|
const value = siblingDoc[field.name][locale];
|
||||||
if (typeof localizedValue === 'undefined') localizedValue = null;
|
const fallbackLocale = req.payload.config.localization && req.payload.config.localization?.fallback && req.fallbackLocale;
|
||||||
siblingDoc[field.name] = localizedValue;
|
|
||||||
|
let hoistedValue = value;
|
||||||
|
|
||||||
|
if (fallbackLocale && fallbackLocale !== locale) {
|
||||||
|
const fallbackValue = siblingDoc[field.name][fallbackLocale];
|
||||||
|
const isNullOrUndefined = typeof value === 'undefined' || value === null;
|
||||||
|
|
||||||
|
if (fallbackValue) {
|
||||||
|
switch (field.type) {
|
||||||
|
case 'text':
|
||||||
|
case 'textarea': {
|
||||||
|
if (value === '' || isNullOrUndefined) {
|
||||||
|
hoistedValue = fallbackValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
if (isNullOrUndefined) {
|
||||||
|
hoistedValue = fallbackValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
siblingDoc[field.name] = hoistedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanitize outgoing data
|
// Sanitize outgoing field value
|
||||||
switch (field.type) {
|
switch (field.type) {
|
||||||
case 'group': {
|
case 'group': {
|
||||||
// Fill groups with empty objects so fields with hooks within groups can populate
|
// Fill groups with empty objects so fields with hooks within groups can populate
|
||||||
@@ -74,7 +101,7 @@ export const promise = async ({
|
|||||||
}
|
}
|
||||||
case 'tabs': {
|
case 'tabs': {
|
||||||
field.tabs.forEach((tab) => {
|
field.tabs.forEach((tab) => {
|
||||||
if (tabHasName(tab) && typeof siblingDoc[tab.name] === 'undefined') {
|
if (tabHasName(tab) && (typeof siblingDoc[tab.name] === 'undefined' || siblingDoc[tab.name] === null)) {
|
||||||
siblingDoc[tab.name] = {};
|
siblingDoc[tab.name] = {};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -98,6 +98,28 @@ describe('Localization', () => {
|
|||||||
expect(localized.title.es).toEqual(spanishTitle);
|
expect(localized.title.es).toEqual(spanishTitle);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fallback to english translation when empty', async () => {
|
||||||
|
const updated = await payload.update<LocalizedPost>({
|
||||||
|
collection,
|
||||||
|
id: post1.id,
|
||||||
|
locale: spanishLocale,
|
||||||
|
data: {
|
||||||
|
title: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updated.title).toEqual(englishTitle);
|
||||||
|
|
||||||
|
const localizedFallback = await payload.findByID<LocalizedPostAllLocale>({
|
||||||
|
collection,
|
||||||
|
id: post1.id,
|
||||||
|
locale: 'all',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(localizedFallback.title.en).toEqual(englishTitle);
|
||||||
|
expect(localizedFallback.title.es).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
describe('querying', () => {
|
describe('querying', () => {
|
||||||
let localizedPost: LocalizedPost;
|
let localizedPost: LocalizedPost;
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user