Fix field/collection translated name not resetting after being removed (#16131)

* reset existing field translations

* remove unused and malformed settings translations

* reset existing collection translations

* safer handling in case translations isn't an array

* inverse locales to reset logic for correctness

* add tests

Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
This commit is contained in:
Azri Kahar
2022-10-26 03:08:09 +08:00
committed by GitHub
parent 12a425a90b
commit 10082ccbaf
5 changed files with 201 additions and 25 deletions

View File

@@ -9,9 +9,6 @@ fields:
options:
iconRight: title
placeholder: $t:field_options.directus_settings.project_name_placeholder
translations:
language: en-US
translations: Name
width: half
- field: project_descriptor
@@ -19,9 +16,6 @@ fields:
options:
iconRight: title
placeholder: $t:field_options.directus_settings.project_name_placeholder
translations:
language: en-US
translations: Name
width: half
- field: project_url
@@ -29,9 +23,6 @@ fields:
options:
iconRight: link
placeholder: https://example.com
translations:
language: en-US
translations: Website
width: half
- field: default_language
@@ -39,9 +30,6 @@ fields:
options:
iconRight: language
placeholder: en-US
translations:
language: en-US
translations: Default Language
width: half
- field: branding_divider
@@ -57,31 +45,20 @@ fields:
- field: project_color
interface: select-color
note: $t:field_options.directus_settings.project_color_note
translations:
language: en-US
translations: Brand Color
width: half
- field: project_logo
interface: file
note: $t:field_options.directus_settings.project_logo_note
translations:
language: en-US
translations: Brand Logo
width: half
- field: public_foreground
interface: file
translations:
language: en-US
translations: Login Foreground
width: half
- field: public_background
interface: file
translations:
language: en-US
translations: Login Background
width: half
- field: public_note

View File

@@ -0,0 +1,82 @@
import { createTestingPinia } from '@pinia/testing';
import { setActivePinia } from 'pinia';
import { beforeEach, expect, test, vi } from 'vitest';
import { i18n } from '@/lang';
import { Collection } from '@directus/shared/types';
import { merge } from 'lodash';
import { useCollectionsStore } from './collections';
beforeEach(() => {
setActivePinia(
createTestingPinia({
createSpy: vi.fn,
stubActions: false,
})
);
});
const mockCollection = {
collection: 'a',
meta: {},
schema: {},
} as Collection;
test('parseField action should translate field name when translations are added then removed', async () => {
const collectionsStore = useCollectionsStore();
const mockCollectionWithTranslations = merge({}, mockCollection, {
meta: {
translations: [
{
language: 'en-US',
translation: 'Collection A en-US',
},
],
},
});
collectionsStore.collections = [mockCollectionWithTranslations].map(collectionsStore.prepareCollectionForApp);
expect(collectionsStore.collections[0].name).toEqual('Collection A en-US');
expect(i18n.global.te(`collection_names.${mockCollection.collection}`)).toBe(true);
const mockCollectionWithMissingTranslations = merge({}, mockCollection, {
meta: {
translations: [
{
language: 'zh-CN',
translation: 'Collection A zh-CN',
},
],
},
});
collectionsStore.collections = [mockCollectionWithMissingTranslations].map(collectionsStore.prepareCollectionForApp);
expect(collectionsStore.collections[0].name).toEqual('A');
expect(i18n.global.te(`collection_names.${mockCollection.collection}`)).toBe(false);
});
test('parseField action should translate field name when all translations are removed', async () => {
const collectionsStore = useCollectionsStore();
const mockCollectionWithTranslations = merge({}, mockCollection, {
meta: {
translations: [
{
language: 'en-US',
translation: 'Collection A en-US',
},
],
},
});
collectionsStore.collections = [mockCollectionWithTranslations].map(collectionsStore.prepareCollectionForApp);
expect(collectionsStore.collections[0].name).toEqual('Collection A en-US');
expect(i18n.global.te(`collection_names.${mockCollection.collection}`)).toBe(true);
const mockCollectionWithoutTranslations = merge({}, mockCollection, {
meta: {
translations: null,
},
});
collectionsStore.collections = [mockCollectionWithoutTranslations].map(collectionsStore.prepareCollectionForApp);
expect(collectionsStore.collections[0].name).toEqual('A');
expect(i18n.global.te(`collection_names.${mockCollection.collection}`)).toBe(false);
});

View File

@@ -53,7 +53,18 @@ export const useCollectionsStore = defineStore({
let name = formatTitle(collection.collection);
const type = getCollectionType(collection);
if (collection.meta && !isNil(collection.meta.translations)) {
const localesToKeep =
collection.meta && !isNil(collection.meta.translations) && Array.isArray(collection.meta.translations)
? collection.meta.translations.map((translation) => translation.language)
: [];
for (const locale of i18n.global.availableLocales) {
if (i18n.global.te(`collection_names.${collection.collection}`, locale) && !localesToKeep.includes(locale)) {
i18n.global.mergeLocaleMessage(locale, { collection_names: { [collection.collection]: undefined } });
}
}
if (collection.meta && !isNil(collection.meta.translations) && Array.isArray(collection.meta.translations)) {
for (let i = 0; i < collection.meta.translations.length; i++) {
const { language, translation, singular, plural } = collection.meta.translations[i];

View File

@@ -0,0 +1,91 @@
import { createTestingPinia } from '@pinia/testing';
import { setActivePinia } from 'pinia';
import { beforeEach, expect, test, vi } from 'vitest';
import { i18n } from '@/lang';
import { Field } from '@directus/shared/types';
import { merge } from 'lodash';
import { useFieldsStore } from './fields';
beforeEach(() => {
setActivePinia(
createTestingPinia({
createSpy: vi.fn,
stubActions: false,
})
);
});
const mockField = {
collection: 'a',
field: 'name',
type: 'string',
schema: {},
meta: {
collection: 'a',
field: 'name',
options: null,
display_options: null,
note: null,
validation_message: null,
},
} as Field;
test('parseField action should translate field name when translations are added then removed', async () => {
const fieldsStore = useFieldsStore();
const mockFieldWithTranslations = merge({}, mockField, {
meta: {
translations: [
{
language: 'en-US',
translation: 'Name en-US',
},
],
},
});
fieldsStore.fields = [mockFieldWithTranslations].map(fieldsStore.parseField);
expect(fieldsStore.fields[0].name).toEqual('Name en-US');
expect(i18n.global.te(`fields.${mockField.collection}.${mockField.field}`)).toBe(true);
const mockFieldWithoutTranslations = merge({}, mockField, {
meta: {
translations: [
{
language: 'zh-CN',
translation: 'Name zh-CN',
},
],
},
});
fieldsStore.fields = [mockFieldWithoutTranslations].map(fieldsStore.parseField);
expect(fieldsStore.fields[0].name).toEqual('Name');
expect(i18n.global.te(`fields.${mockField.collection}.${mockField.field}`)).toBe(false);
});
test('parseField action should translate field name when translations are added and reset when removed', async () => {
const fieldsStore = useFieldsStore();
const mockFieldWithTranslations = merge({}, mockField, {
meta: {
translations: [
{
language: 'en-US',
translation: 'name en-US',
},
],
},
});
fieldsStore.fields = [mockFieldWithTranslations].map(fieldsStore.parseField);
expect(fieldsStore.fields[0].name).toEqual('name en-US');
expect(i18n.global.te(`fields.${mockField.collection}.${mockField.field}`)).toBe(true);
const mockFieldWithoutTranslations = merge({}, mockField, {
meta: {
translations: null,
},
});
fieldsStore.fields = [mockFieldWithoutTranslations].map(fieldsStore.parseField);
expect(fieldsStore.fields[0].name).toEqual('Name');
expect(i18n.global.te(`fields.${mockField.collection}.${mockField.field}`)).toBe(false);
});

View File

@@ -72,7 +72,22 @@ export const useFieldsStore = defineStore({
parseField(field: FieldRaw): Field {
let name = formatTitle(field.field);
if (field.meta && !isNil(field.meta.translations) && field.meta.translations.length > 0) {
const localesToKeep =
field.meta && !isNil(field.meta.translations) && Array.isArray(field.meta.translations)
? field.meta.translations.map((translation) => translation.language)
: [];
for (const locale of i18n.global.availableLocales) {
if (
i18n.global.te(`fields.${field.collection}.${field.field}`, locale) &&
!localesToKeep.includes(locale) &&
!field.meta?.system
) {
i18n.global.mergeLocaleMessage(locale, { fields: { [field.collection]: { [field.field]: undefined } } });
}
}
if (field.meta && !isNil(field.meta.translations) && Array.isArray(field.meta.translations)) {
for (let i = 0; i < field.meta.translations.length; i++) {
const { language, translation } = field.meta.translations[i];