Skip to content
This repository was archived by the owner on Sep 9, 2024. It is now read-only.

Commit a31a47b

Browse files
authored
feat: i18n better locale error handling (#949)
1 parent f29a5f3 commit a31a47b

File tree

10 files changed

+294
-55
lines changed

10 files changed

+294
-55
lines changed

packages/core/dev-test/backends/proxy/config.yml

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ collections:
504504
name: file
505505
widget: file
506506
- name: i18n_playground
507-
label: i18n Playground
507+
label: i18n (Multiple Files)
508508
i18n: true
509509
folder: packages/core/dev-test/backends/proxy/_i18n_playground
510510
identifier_field: slug
@@ -524,6 +524,54 @@ collections:
524524
label: Date
525525
widget: datetime
526526
i18n: duplicate
527+
- name: i18n_playground_multiple_folders
528+
label: i18n (Multiple Folders)
529+
i18n:
530+
structure: multiple_folders
531+
locales: [en, de, fr]
532+
defaultLocale: en
533+
folder: packages/core/dev-test/backends/proxy/_i18n_playground_multiple_folders
534+
identifier_field: slug
535+
create: true
536+
fields:
537+
# The slug field will be omitted from the translation.
538+
- name: slug
539+
label: Slug
540+
widget: string
541+
# same as 'i18n: translate'. Allows translation of the description field
542+
- name: description
543+
label: Description
544+
widget: text
545+
i18n: true
546+
# The date field will be duplicated from the default locale.
547+
- name: date
548+
label: Date
549+
widget: datetime
550+
i18n: duplicate
551+
- name: i18n_playground_single_file
552+
label: i18n (Single File)
553+
i18n:
554+
structure: single_file
555+
locales: [en, de, fr]
556+
defaultLocale: en
557+
folder: packages/core/dev-test/backends/proxy/_i18n_playground_multiple_folders
558+
identifier_field: slug
559+
create: true
560+
fields:
561+
# The slug field will be omitted from the translation.
562+
- name: slug
563+
label: Slug
564+
widget: string
565+
# same as 'i18n: translate'. Allows translation of the description field
566+
- name: description
567+
label: Description
568+
widget: text
569+
i18n: true
570+
# The date field will be duplicated from the default locale.
571+
- name: date
572+
label: Date
573+
widget: datetime
574+
i18n: duplicate
527575
- name: pages
528576
label: Nested Pages
529577
label_singular: 'Page'

packages/core/src/__tests__/testConfig.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1794,6 +1794,62 @@ const testConfig: Config<RelationKitchenSinkPostField> = {
17941794
},
17951795
],
17961796
},
1797+
{
1798+
name: 'i18n_playground_multiple_folders',
1799+
label: 'i18n (Multiple Folders)',
1800+
i18n: {
1801+
structure: 'multiple_folders',
1802+
locales: ['en', 'de', 'fr'],
1803+
defaultLocale: 'en',
1804+
},
1805+
folder: 'packages/core/dev-test/backends/proxy/_i18n_playground_multiple_folders',
1806+
identifier_field: 'slug',
1807+
create: true,
1808+
fields: [
1809+
{
1810+
name: 'slug',
1811+
label: 'Slug',
1812+
widget: 'string',
1813+
},
1814+
{
1815+
name: 'description',
1816+
label: 'Description',
1817+
widget: 'text',
1818+
i18n: true,
1819+
},
1820+
{
1821+
name: 'date',
1822+
label: 'Date',
1823+
widget: 'datetime',
1824+
i18n: 'duplicate',
1825+
},
1826+
],
1827+
},
1828+
{
1829+
name: 'i18n_playground_single_file',
1830+
label: 'i18n (Single File)',
1831+
i18n: {
1832+
structure: 'single_file',
1833+
locales: ['en', 'de', 'fr'],
1834+
defaultLocale: 'en',
1835+
},
1836+
folder: 'packages/core/dev-test/backends/proxy/_i18n_playground_single_file',
1837+
identifier_field: 'slug',
1838+
create: true,
1839+
fields: [
1840+
{
1841+
name: 'slug',
1842+
label: 'Slug',
1843+
widget: 'string',
1844+
},
1845+
{
1846+
name: 'description',
1847+
label: 'Description',
1848+
widget: 'text',
1849+
i18n: true,
1850+
},
1851+
],
1852+
},
17971853
],
17981854
};
17991855

packages/core/src/components/entry-editor/EditorInterface.css

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,22 @@
1414
}
1515

1616
& .CMS_Editor_i18n {
17-
@apply flex
17+
@apply hidden
1818
w-full
1919
overflow-y-auto
2020
h-main-mobile
2121
md:h-main;
22+
23+
&.CMS_Editor_i18n-active {
24+
@apply flex;
25+
}
2226
}
2327

2428
& .CMS_Editor_mobile-i18n {
2529
@apply flex
2630
w-full
2731
overflow-y-auto
28-
h-main-mobile
29-
md:hidden;
32+
h-main-mobile;
3033
}
3134

3235
& .CMS_Editor_root {

packages/core/src/components/entry-editor/EditorInterface.tsx

Lines changed: 77 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ScrollSyncPane } from 'react-scroll-sync';
44
import { EDITOR_SIZE_COMPACT } from '@staticcms/core/constants/views';
55
import { summaryFormatter } from '@staticcms/core/lib/formatters';
66
import useBreadcrumbs from '@staticcms/core/lib/hooks/useBreadcrumbs';
7+
import { useIsSmallScreen } from '@staticcms/core/lib/hooks/useMediaQuery';
78
import { getI18nInfo, getPreviewEntry, hasI18n } from '@staticcms/core/lib/i18n';
89
import classNames from '@staticcms/core/lib/util/classNames.util';
910
import {
@@ -34,6 +35,7 @@ export const classes = generateClassNames('Editor', [
3435
'root',
3536
'default',
3637
'i18n',
38+
'i18n-active',
3739
'mobile-i18n',
3840
'split-view',
3941
'mobile-preview',
@@ -120,10 +122,12 @@ const EditorInterface = ({
120122
}: TranslatedProps<EditorInterfaceProps>) => {
121123
const config = useAppSelector(selectConfig);
122124

125+
const isSmallScreen = useIsSmallScreen();
126+
123127
const { locales, defaultLocale } = useMemo(() => getI18nInfo(collection), [collection]) ?? {};
124128
const translatedLocales = useMemo(
125-
() => locales?.filter(locale => locale !== defaultLocale) ?? [],
126-
[locales, defaultLocale],
129+
() => (isSmallScreen ? locales : locales?.filter(locale => locale !== defaultLocale)) ?? [],
130+
[isSmallScreen, locales, defaultLocale],
127131
);
128132

129133
const [previewActive, setPreviewActive] = useState(
@@ -140,6 +144,10 @@ const EditorInterface = ({
140144
(i18nActive ? translatedLocales?.[0] : defaultLocale) ?? 'en',
141145
);
142146

147+
useEffect(() => {
148+
setSelectedLocale((i18nActive ? translatedLocales?.[0] : defaultLocale) ?? 'en');
149+
}, [defaultLocale, i18nActive, translatedLocales]);
150+
143151
useEffect(() => {
144152
loadScroll();
145153
}, [loadScroll]);
@@ -293,24 +301,44 @@ const EditorInterface = ({
293301
);
294302

295303
const editorLocale = useMemo(
296-
() => (
297-
<div key={selectedLocale} className={classes.i18n}>
298-
<EditorControlPane
299-
collection={collection}
300-
entry={entry}
301-
fields={fields}
302-
fieldsErrors={fieldsErrors}
303-
locale={selectedLocale}
304-
onLocaleChange={handleLocaleChange}
305-
submitted={submitted}
306-
canChangeLocale
307-
context="i18nSplit"
308-
hideBorder
309-
t={t}
310-
/>
311-
</div>
312-
),
313-
[collection, entry, fields, fieldsErrors, handleLocaleChange, selectedLocale, submitted, t],
304+
() =>
305+
locales
306+
?.filter(l => l !== defaultLocale)
307+
.map(locale => (
308+
<div
309+
key={locale}
310+
className={classNames(
311+
classes.i18n,
312+
selectedLocale === locale && classes['i18n-active'],
313+
)}
314+
>
315+
<EditorControlPane
316+
collection={collection}
317+
entry={entry}
318+
fields={fields}
319+
fieldsErrors={fieldsErrors}
320+
locale={locale}
321+
onLocaleChange={handleLocaleChange}
322+
submitted={submitted}
323+
canChangeLocale
324+
context="i18nSplit"
325+
hideBorder
326+
t={t}
327+
/>
328+
</div>
329+
)),
330+
[
331+
collection,
332+
defaultLocale,
333+
entry,
334+
fields,
335+
fieldsErrors,
336+
handleLocaleChange,
337+
locales,
338+
selectedLocale,
339+
submitted,
340+
t,
341+
],
314342
);
315343

316344
const previewEntry = useMemo(
@@ -320,24 +348,35 @@ const EditorInterface = ({
320348
);
321349

322350
const mobileLocaleEditor = useMemo(
323-
() => (
324-
<div key={selectedLocale} className={classes['mobile-i18n']}>
325-
<EditorControlPane
326-
collection={collection}
327-
entry={entry}
328-
fields={fields}
329-
fieldsErrors={fieldsErrors}
330-
locale={selectedLocale}
331-
onLocaleChange={handleLocaleChange}
332-
allowDefaultLocale
333-
submitted={submitted}
334-
canChangeLocale
335-
hideBorder
336-
t={t}
337-
/>
338-
</div>
339-
),
340-
[collection, entry, fields, fieldsErrors, handleLocaleChange, selectedLocale, submitted, t],
351+
() =>
352+
isSmallScreen ? (
353+
<div key={selectedLocale} className={classes['mobile-i18n']}>
354+
<EditorControlPane
355+
collection={collection}
356+
entry={entry}
357+
fields={fields}
358+
fieldsErrors={fieldsErrors}
359+
locale={selectedLocale}
360+
onLocaleChange={handleLocaleChange}
361+
allowDefaultLocale
362+
submitted={submitted}
363+
canChangeLocale
364+
hideBorder
365+
t={t}
366+
/>
367+
</div>
368+
) : null,
369+
[
370+
collection,
371+
entry,
372+
fields,
373+
fieldsErrors,
374+
handleLocaleChange,
375+
isSmallScreen,
376+
selectedLocale,
377+
submitted,
378+
t,
379+
],
341380
);
342381

343382
const editorWithPreview = (

packages/core/src/components/entry-editor/editor-control-pane/EditorControlPane.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ const EditorControlPane = ({
108108
{i18n?.locales && locale ? (
109109
<div className={classes.locale_dropdown_wrapper}>
110110
<LocaleDropdown
111+
locale={locale}
111112
locales={i18n.locales}
112113
defaultLocale={i18n.defaultLocale}
113114
dropdownText={t('editor.editorControlPane.i18n.writingInLocale', {

packages/core/src/components/entry-editor/editor-control-pane/LocaleDropdown.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
11
.CMS_LocaleDropdown_root {
2+
&:not(.CMS_LocaleDropdown_no-edit) {
3+
@apply flex
4+
gap-2
5+
items-center;
6+
}
7+
}
8+
9+
.CMS_LocaleDropdown_dropdown {
10+
}
11+
12+
.CMS_LocaleDropdown_errors-icon {
13+
@apply w-7
14+
h-7
15+
text-red-500;
216
}
317

418
.CMS_LocaleDropdown_no-edit {

0 commit comments

Comments
 (0)