mirror of
https://github.com/strapi/strapi.git
synced 2025-12-13 16:08:11 +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 { CaretDown } from '@strapi/icons';
|
||||
import { useQueryParams } from '@strapi/admin/strapi-admin';
|
||||
import { Flex, Menu, Typography, useCollator } from '@strapi/design-system';
|
||||
import { stringify } from 'qs';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { Locale } from '../../../shared/contracts/locales';
|
||||
import { useGetLocalesQuery } from '../services/locales';
|
||||
import { getTranslation } from '../utils/getTranslation';
|
||||
|
||||
import type { I18nBaseQuery } from '../types';
|
||||
|
||||
interface LocaleListCellProps {
|
||||
localizations: { locale: string }[];
|
||||
locale: string;
|
||||
documentId: string;
|
||||
}
|
||||
|
||||
const LocaleListCell = ({ locale: currentLocale, localizations }: LocaleListCellProps) => {
|
||||
const { locale: language } = useIntl();
|
||||
const LocaleListCell = ({
|
||||
locale: currentLocale,
|
||||
localizations,
|
||||
documentId,
|
||||
}: LocaleListCellProps) => {
|
||||
const { locale: language, formatMessage } = useIntl();
|
||||
const { data: locales = [] } = useGetLocalesQuery();
|
||||
const navigate = useNavigate();
|
||||
const [{ query }] = useQueryParams<I18nBaseQuery>();
|
||||
const formatter = useCollator(language, {
|
||||
sensitivity: 'base',
|
||||
});
|
||||
@ -24,50 +35,76 @@ const LocaleListCell = ({ locale: currentLocale, localizations }: LocaleListCell
|
||||
const availableLocales = localizations.map((loc) => loc.locale);
|
||||
|
||||
const localesForDocument = locales
|
||||
.reduce<Locale[]>((acc, locale) => {
|
||||
.reduce<Array<{ code: string; name: string }>>((acc, locale) => {
|
||||
const createdLocale = [currentLocale, ...availableLocales].find((loc) => {
|
||||
return loc === locale.code;
|
||||
});
|
||||
|
||||
if (createdLocale) {
|
||||
acc.push(locale);
|
||||
const name = locale.isDefault ? `${locale.name} (default)` : locale.name;
|
||||
acc.push({ code: locale.code, name });
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, [])
|
||||
.map((locale) => {
|
||||
if (locale.isDefault) {
|
||||
return `${locale.name} (default)`;
|
||||
}
|
||||
.toSorted((a, b) => formatter.compare(a.name, b.name));
|
||||
|
||||
return locale.name;
|
||||
})
|
||||
.toSorted((a, b) => formatter.compare(a, b));
|
||||
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 formatMessage(
|
||||
{
|
||||
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 (
|
||||
<Popover.Root>
|
||||
<Popover.Trigger>
|
||||
<Button variant="ghost" type="button" onClick={(e) => e.stopPropagation()}>
|
||||
<Flex minWidth="100%" alignItems="center" justifyContent="center" fontWeight="regular">
|
||||
<Typography textColor="neutral800" ellipsis marginRight={2}>
|
||||
{localesForDocument.join(', ')}
|
||||
<Menu.Root>
|
||||
<Menu.Trigger>
|
||||
<Flex minWidth="100%" alignItems="center" justifyContent="center" fontWeight="regular">
|
||||
<Typography textColor="neutral800" ellipsis marginRight={2}>
|
||||
{getDisplayText()}
|
||||
</Typography>
|
||||
</Flex>
|
||||
</Menu.Trigger>
|
||||
<Menu.Content>
|
||||
{localesForDocument.map(({ code, name }) => (
|
||||
<Menu.Item
|
||||
key={code}
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
handleLocaleClick(code);
|
||||
}}
|
||||
>
|
||||
<Typography textColor="neutral800" fontWeight="regular">
|
||||
{name}
|
||||
</Typography>
|
||||
<Flex>
|
||||
<CaretDown width="1.2rem" height="1.2rem" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Button>
|
||||
</Popover.Trigger>
|
||||
<Popover.Content sideOffset={16}>
|
||||
<ul>
|
||||
{localesForDocument.map((name) => (
|
||||
<Box key={name} padding={3} tag="li">
|
||||
<Typography>{name}</Typography>
|
||||
</Box>
|
||||
))}
|
||||
</ul>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.Content>
|
||||
</Menu.Root>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -13,36 +13,79 @@ jest.mock('@strapi/content-manager/strapi-admin', () => ({
|
||||
availableLocales: [
|
||||
{ locale: 'en', status: 'draft' },
|
||||
{ locale: 'fr', status: 'published' },
|
||||
{ locale: 'de', status: 'draft' },
|
||||
{ locale: 'es', status: 'published' },
|
||||
],
|
||||
},
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('LocaleListCell', () => {
|
||||
it('renders a button with all the names of the locales that are available for the document', async () => {
|
||||
render(<LocaleListCell localizations={[{ locale: 'en' }, { locale: 'fr' }]} locale="en" />);
|
||||
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"
|
||||
documentId="123"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByRole('button', { name: 'English (default), Français' })
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByText('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 () => {
|
||||
const { user } = render(
|
||||
<LocaleListCell localizations={[{ locale: 'en' }, { locale: 'fr' }]} locale="en" />
|
||||
<LocaleListCell
|
||||
localizations={[{ locale: 'en' }, { locale: 'fr' }]}
|
||||
locale="en"
|
||||
documentId="123"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByRole('button', { name: 'English (default), Français' })
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByText('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.getAllByRole('listitem')).toHaveLength(2);
|
||||
expect(screen.getAllByRole('listitem').at(0)).toHaveTextContent('English (default)');
|
||||
expect(screen.getAllByRole('listitem').at(1)).toHaveTextContent('Français');
|
||||
expect(screen.getByRole('menu')).toBeInTheDocument();
|
||||
expect(screen.getAllByRole('menuitem')).toHaveLength(2);
|
||||
expect(screen.getAllByRole('menuitem').at(0)).toHaveTextContent('English (default)');
|
||||
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,
|
||||
name: 'locales',
|
||||
// @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,
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
"CMEditViewBulkLocale.continue-confirmation": "Are you sure you want to continue?",
|
||||
"CMEditViewLocalePicker.locale.create": "Create <bold>{locale}</bold> locale",
|
||||
"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.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).",
|
||||
|
||||
@ -20,6 +20,22 @@ const LOCALES = [
|
||||
createdAt: '',
|
||||
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;
|
||||
|
||||
export const server = setupServer(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user