mirror of
https://github.com/strapi/strapi.git
synced 2025-12-14 08:44:16 +00:00
fix: behaviour of i18n "Available In" column (#24620)
* fix: behaviour of i18n "Available In" column * chore: use better formating for translation of label
This commit is contained in:
parent
ddd1f2d822
commit
ec828411d4
@ -1,18 +1,29 @@
|
|||||||
import { Box, Flex, Popover, Typography, useCollator, Button } from '@strapi/design-system';
|
import { useQueryParams } from '@strapi/admin/strapi-admin';
|
||||||
import { CaretDown } from '@strapi/icons';
|
import { Flex, Menu, Typography, useCollator } from '@strapi/design-system';
|
||||||
|
import { stringify } from 'qs';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { Locale } from '../../../shared/contracts/locales';
|
|
||||||
import { useGetLocalesQuery } from '../services/locales';
|
import { useGetLocalesQuery } from '../services/locales';
|
||||||
|
import { getTranslation } from '../utils/getTranslation';
|
||||||
|
|
||||||
|
import type { I18nBaseQuery } from '../types';
|
||||||
|
|
||||||
interface LocaleListCellProps {
|
interface LocaleListCellProps {
|
||||||
localizations: { locale: string }[];
|
localizations: { locale: string }[];
|
||||||
locale: string;
|
locale: string;
|
||||||
|
documentId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LocaleListCell = ({ locale: currentLocale, localizations }: LocaleListCellProps) => {
|
const LocaleListCell = ({
|
||||||
const { locale: language } = useIntl();
|
locale: currentLocale,
|
||||||
|
localizations,
|
||||||
|
documentId,
|
||||||
|
}: LocaleListCellProps) => {
|
||||||
|
const { locale: language, formatMessage } = useIntl();
|
||||||
const { data: locales = [] } = useGetLocalesQuery();
|
const { data: locales = [] } = useGetLocalesQuery();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [{ query }] = useQueryParams<I18nBaseQuery>();
|
||||||
const formatter = useCollator(language, {
|
const formatter = useCollator(language, {
|
||||||
sensitivity: 'base',
|
sensitivity: 'base',
|
||||||
});
|
});
|
||||||
@ -24,50 +35,76 @@ const LocaleListCell = ({ locale: currentLocale, localizations }: LocaleListCell
|
|||||||
const availableLocales = localizations.map((loc) => loc.locale);
|
const availableLocales = localizations.map((loc) => loc.locale);
|
||||||
|
|
||||||
const localesForDocument = locales
|
const localesForDocument = locales
|
||||||
.reduce<Locale[]>((acc, locale) => {
|
.reduce<Array<{ code: string; name: string }>>((acc, locale) => {
|
||||||
const createdLocale = [currentLocale, ...availableLocales].find((loc) => {
|
const createdLocale = [currentLocale, ...availableLocales].find((loc) => {
|
||||||
return loc === locale.code;
|
return loc === locale.code;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (createdLocale) {
|
if (createdLocale) {
|
||||||
acc.push(locale);
|
const name = locale.isDefault ? `${locale.name} (default)` : locale.name;
|
||||||
|
acc.push({ code: locale.code, name });
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, [])
|
}, [])
|
||||||
.map((locale) => {
|
.toSorted((a, b) => formatter.compare(a.name, b.name));
|
||||||
if (locale.isDefault) {
|
|
||||||
return `${locale.name} (default)`;
|
const getDisplayText = () => {
|
||||||
|
const displayedLocales = localesForDocument.slice(0, 2);
|
||||||
|
const remainingCount = localesForDocument.length - 2;
|
||||||
|
|
||||||
|
const baseText = displayedLocales.map(({ name }) => name).join(', ');
|
||||||
|
|
||||||
|
if (remainingCount <= 0) {
|
||||||
|
return baseText;
|
||||||
}
|
}
|
||||||
|
|
||||||
return locale.name;
|
return formatMessage(
|
||||||
})
|
{
|
||||||
.toSorted((a, b) => formatter.compare(a, b));
|
id: getTranslation('CMListView.popover.display-locales.more'),
|
||||||
|
defaultMessage: '{locales} + {count} more',
|
||||||
|
},
|
||||||
|
{ locales: baseText, count: remainingCount }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLocaleClick = (localeCode: string) => {
|
||||||
|
navigate({
|
||||||
|
pathname: documentId,
|
||||||
|
search: stringify({
|
||||||
|
plugins: {
|
||||||
|
...query.plugins,
|
||||||
|
i18n: { locale: localeCode },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover.Root>
|
<Menu.Root>
|
||||||
<Popover.Trigger>
|
<Menu.Trigger>
|
||||||
<Button variant="ghost" type="button" onClick={(e) => e.stopPropagation()}>
|
|
||||||
<Flex minWidth="100%" alignItems="center" justifyContent="center" fontWeight="regular">
|
<Flex minWidth="100%" alignItems="center" justifyContent="center" fontWeight="regular">
|
||||||
<Typography textColor="neutral800" ellipsis marginRight={2}>
|
<Typography textColor="neutral800" ellipsis marginRight={2}>
|
||||||
{localesForDocument.join(', ')}
|
{getDisplayText()}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Flex>
|
|
||||||
<CaretDown width="1.2rem" height="1.2rem" />
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Menu.Trigger>
|
||||||
</Button>
|
<Menu.Content>
|
||||||
</Popover.Trigger>
|
{localesForDocument.map(({ code, name }) => (
|
||||||
<Popover.Content sideOffset={16}>
|
<Menu.Item
|
||||||
<ul>
|
key={code}
|
||||||
{localesForDocument.map((name) => (
|
onClick={(e: React.MouseEvent) => {
|
||||||
<Box key={name} padding={3} tag="li">
|
e.stopPropagation();
|
||||||
<Typography>{name}</Typography>
|
handleLocaleClick(code);
|
||||||
</Box>
|
}}
|
||||||
|
>
|
||||||
|
<Typography textColor="neutral800" fontWeight="regular">
|
||||||
|
{name}
|
||||||
|
</Typography>
|
||||||
|
</Menu.Item>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</Menu.Content>
|
||||||
</Popover.Content>
|
</Menu.Root>
|
||||||
</Popover.Root>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -13,36 +13,79 @@ jest.mock('@strapi/content-manager/strapi-admin', () => ({
|
|||||||
availableLocales: [
|
availableLocales: [
|
||||||
{ locale: 'en', status: 'draft' },
|
{ locale: 'en', status: 'draft' },
|
||||||
{ locale: 'fr', status: 'published' },
|
{ locale: 'fr', status: 'published' },
|
||||||
|
{ locale: 'de', status: 'draft' },
|
||||||
|
{ locale: 'es', status: 'published' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('LocaleListCell', () => {
|
describe('LocaleListCell', () => {
|
||||||
it('renders a button with all the names of the locales that are available for the document', async () => {
|
it('renders a button with all the names of the locales that are available for the document when there are 2 or fewer locales', async () => {
|
||||||
render(<LocaleListCell localizations={[{ locale: 'en' }, { locale: 'fr' }]} locale="en" />);
|
render(
|
||||||
|
<LocaleListCell
|
||||||
|
localizations={[{ locale: 'en' }, { locale: 'fr' }]}
|
||||||
|
locale="en"
|
||||||
|
documentId="123"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
expect(
|
expect(await screen.findByText('English (default), Français')).toBeInTheDocument();
|
||||||
await screen.findByRole('button', { name: 'English (default), Français' })
|
|
||||||
).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(screen.queryByRole('list')).not.toBeInTheDocument();
|
expect(screen.queryByRole('menu')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a button with only the first 2 locales and "+ X more" text when there are more than 2 locales', async () => {
|
||||||
|
render(
|
||||||
|
<LocaleListCell
|
||||||
|
localizations={[{ locale: 'en' }, { locale: 'fr' }, { locale: 'de' }, { locale: 'es' }]}
|
||||||
|
locale="en"
|
||||||
|
documentId="123"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const button = await screen.findByText('Deutsch, English (default) + 2 more');
|
||||||
|
expect(button).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.queryByRole('menu')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a list of the locales available on the document when the button is clicked', async () => {
|
it('renders a list of the locales available on the document when the button is clicked', async () => {
|
||||||
const { user } = render(
|
const { user } = render(
|
||||||
<LocaleListCell localizations={[{ locale: 'en' }, { locale: 'fr' }]} locale="en" />
|
<LocaleListCell
|
||||||
|
localizations={[{ locale: 'en' }, { locale: 'fr' }]}
|
||||||
|
locale="en"
|
||||||
|
documentId="123"
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(await screen.findByText('English (default), Français')).toBeInTheDocument();
|
||||||
await screen.findByRole('button', { name: 'English (default), Français' })
|
|
||||||
).toBeInTheDocument();
|
|
||||||
|
|
||||||
await user.click(screen.getByRole('button'));
|
await user.click(screen.getByText('English (default), Français'));
|
||||||
|
|
||||||
expect(screen.getByRole('list')).toBeInTheDocument();
|
expect(screen.getByRole('menu')).toBeInTheDocument();
|
||||||
expect(screen.getAllByRole('listitem')).toHaveLength(2);
|
expect(screen.getAllByRole('menuitem')).toHaveLength(2);
|
||||||
expect(screen.getAllByRole('listitem').at(0)).toHaveTextContent('English (default)');
|
expect(screen.getAllByRole('menuitem').at(0)).toHaveTextContent('English (default)');
|
||||||
expect(screen.getAllByRole('listitem').at(1)).toHaveTextContent('Français');
|
expect(screen.getAllByRole('menuitem').at(1)).toHaveTextContent('Français');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders clickable menu items for each locale in the menu', async () => {
|
||||||
|
const { user } = render(
|
||||||
|
<LocaleListCell
|
||||||
|
localizations={[{ locale: 'en' }, { locale: 'fr' }]}
|
||||||
|
locale="en"
|
||||||
|
documentId="123"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuTrigger = await screen.findByText('English (default), Français');
|
||||||
|
await user.click(menuTrigger);
|
||||||
|
|
||||||
|
// Check that locale items are now menu items
|
||||||
|
const englishMenuItem = screen.getByRole('menuitem', { name: 'English (default)' });
|
||||||
|
const frenchMenuItem = screen.getByRole('menuitem', { name: 'Français' });
|
||||||
|
|
||||||
|
expect(englishMenuItem).toBeInTheDocument();
|
||||||
|
expect(frenchMenuItem).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -37,7 +37,9 @@ const addColumnToTableHook = ({ displayedHeaders, layout }: AddColumnToTableHook
|
|||||||
sortable: false,
|
sortable: false,
|
||||||
name: 'locales',
|
name: 'locales',
|
||||||
// @ts-expect-error – ID is seen as number | string; this will change when we move the type over.
|
// @ts-expect-error – ID is seen as number | string; this will change when we move the type over.
|
||||||
cellFormatter: (props, _header, meta) => <LocaleListCell {...props} {...meta} />,
|
cellFormatter: (props, _header, meta) => (
|
||||||
|
<LocaleListCell {...props} {...meta} documentId={props.documentId} />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
layout,
|
layout,
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
"CMEditViewBulkLocale.continue-confirmation": "Are you sure you want to continue?",
|
"CMEditViewBulkLocale.continue-confirmation": "Are you sure you want to continue?",
|
||||||
"CMEditViewLocalePicker.locale.create": "Create <bold>{locale}</bold> locale",
|
"CMEditViewLocalePicker.locale.create": "Create <bold>{locale}</bold> locale",
|
||||||
"CMListView.popover.display-locales.label": "Display translated locales",
|
"CMListView.popover.display-locales.label": "Display translated locales",
|
||||||
|
"CMListView.popover.display-locales.more": "{locales} + {count} more",
|
||||||
"CheckboxConfirmation.Modal.body": "Do you want to disable it?",
|
"CheckboxConfirmation.Modal.body": "Do you want to disable it?",
|
||||||
"CheckboxConfirmation.Modal.button-confirm": "Yes, disable",
|
"CheckboxConfirmation.Modal.button-confirm": "Yes, disable",
|
||||||
"CheckboxConfirmation.Modal.content": "Disabling localization will engender the deletion of all your content but the one associated to your default locale (if existing).",
|
"CheckboxConfirmation.Modal.content": "Disabling localization will engender the deletion of all your content but the one associated to your default locale (if existing).",
|
||||||
|
|||||||
@ -20,6 +20,22 @@ const LOCALES = [
|
|||||||
createdAt: '',
|
createdAt: '',
|
||||||
updatedAt: '',
|
updatedAt: '',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
code: 'de',
|
||||||
|
name: 'Deutsch',
|
||||||
|
isDefault: false,
|
||||||
|
createdAt: '',
|
||||||
|
updatedAt: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
code: 'es',
|
||||||
|
name: 'Español',
|
||||||
|
isDefault: false,
|
||||||
|
createdAt: '',
|
||||||
|
updatedAt: '',
|
||||||
|
},
|
||||||
] satisfies GetLocales.Response;
|
] satisfies GetLocales.Response;
|
||||||
|
|
||||||
export const server = setupServer(
|
export const server = setupServer(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user