mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Translation Strings Improvements (#13920)
* fix translations string being null * clean up watcher a bit * add sort prop to list interface * use sort for list interface in translation strings
This commit is contained in:
@@ -1,24 +1,24 @@
|
||||
<template>
|
||||
<div class="repeater">
|
||||
<v-notice v-if="(Array.isArray(value) && value.length === 0) || value == null">
|
||||
<v-notice v-if="(Array.isArray(internalValue) && internalValue.length === 0) || internalValue == null">
|
||||
{{ placeholder }}
|
||||
</v-notice>
|
||||
<v-notice v-else-if="!Array.isArray(value)" type="warning">
|
||||
<v-notice v-else-if="!Array.isArray(internalValue)" type="warning">
|
||||
<p>{{ t('interfaces.list.incompatible_data') }}</p>
|
||||
</v-notice>
|
||||
|
||||
<v-list v-if="Array.isArray(value) && value.length > 0">
|
||||
<v-list v-if="Array.isArray(internalValue) && internalValue.length > 0">
|
||||
<draggable
|
||||
:disabled="disabled"
|
||||
:force-fallback="true"
|
||||
:model-value="value"
|
||||
:model-value="internalValue"
|
||||
item-key="id"
|
||||
handle=".drag-handle"
|
||||
@update:model-value="$emit('input', $event)"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<v-list-item :dense="value.length > 4" block @click="openItem(index)">
|
||||
<v-icon v-if="!disabled" name="drag_handle" class="drag-handle" left @click.stop="() => {}" />
|
||||
<v-list-item :dense="internalValue.length > 4" block @click="openItem(index)">
|
||||
<v-icon v-if="!disabled && !sort" name="drag_handle" class="drag-handle" left @click.stop="() => {}" />
|
||||
<render-template :fields="fields" :item="{ ...defaults, ...element }" :template="templateWithDefaults" />
|
||||
<div class="spacer" />
|
||||
<v-icon v-if="!disabled" name="close" @click.stop="removeItem(element)" />
|
||||
@@ -44,7 +44,7 @@
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<v-button v-tooltip.bottom="t('save')" icon rounded @click="saveItem(active)">
|
||||
<v-button v-tooltip.bottom="t('save')" icon rounded @click="saveItem(active!)">
|
||||
<v-icon name="check" />
|
||||
</v-button>
|
||||
</template>
|
||||
@@ -85,7 +85,7 @@ import { i18n } from '@/lang';
|
||||
import { renderStringTemplate } from '@/utils/render-string-template';
|
||||
import hideDragImage from '@/utils/hide-drag-image';
|
||||
import formatTitle from '@directus/format-title';
|
||||
import { isEqual } from 'lodash';
|
||||
import { isEqual, sortBy } from 'lodash';
|
||||
|
||||
export default defineComponent({
|
||||
components: { Draggable },
|
||||
@@ -106,6 +106,10 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: () => i18n.global.t('create_new'),
|
||||
},
|
||||
sort: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: null,
|
||||
@@ -172,12 +176,23 @@ export default defineComponent({
|
||||
})
|
||||
);
|
||||
|
||||
const internalValue = computed({
|
||||
get: () => {
|
||||
if (props.fields && props.sort) return sortBy(value.value, props.sort);
|
||||
return value.value;
|
||||
},
|
||||
set: (newVal) => {
|
||||
value.value = props.fields && props.sort ? sortBy(value.value, props.sort) : newVal;
|
||||
},
|
||||
});
|
||||
|
||||
const isNewItem = ref(false);
|
||||
const edits = ref({});
|
||||
const confirmDiscard = ref(false);
|
||||
|
||||
return {
|
||||
t,
|
||||
internalValue,
|
||||
updateValues,
|
||||
removeItem,
|
||||
addNew,
|
||||
@@ -205,7 +220,7 @@ export default defineComponent({
|
||||
function openItem(index: number) {
|
||||
isNewItem.value = false;
|
||||
|
||||
edits.value = { ...props.value[index] };
|
||||
edits.value = { ...internalValue.value[index] };
|
||||
active.value = index;
|
||||
}
|
||||
|
||||
@@ -222,7 +237,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function checkDiscard() {
|
||||
if (active.value !== null && !isEqual(edits.value, props.value[active.value])) {
|
||||
if (active.value !== null && !isEqual(edits.value, internalValue.value[active.value])) {
|
||||
confirmDiscard.value = true;
|
||||
} else {
|
||||
closeDrawer();
|
||||
@@ -235,20 +250,24 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function updateValues(index: number, updatedValues: any) {
|
||||
emitValue(
|
||||
props.value.map((item: any, i: number) => {
|
||||
if (i === index) {
|
||||
return updatedValues;
|
||||
}
|
||||
const newValue = internalValue.value.map((item: any, i: number) => {
|
||||
if (i === index) {
|
||||
return updatedValues;
|
||||
}
|
||||
|
||||
return item;
|
||||
})
|
||||
);
|
||||
return item;
|
||||
});
|
||||
|
||||
if (props.fields && props.sort) {
|
||||
emitValue(sortBy(newValue, props.sort));
|
||||
} else {
|
||||
emitValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
function removeItem(item: Record<string, any>) {
|
||||
if (value.value) {
|
||||
emitValue(props.value.filter((i) => i !== item));
|
||||
emitValue(internalValue.value.filter((i) => i !== item));
|
||||
} else {
|
||||
emitValue(null);
|
||||
}
|
||||
@@ -263,10 +282,10 @@ export default defineComponent({
|
||||
newDefaults[field.field!] = field.schema?.default_value;
|
||||
});
|
||||
|
||||
if (Array.isArray(props.value)) {
|
||||
emitValue([...props.value, newDefaults]);
|
||||
if (Array.isArray(internalValue.value)) {
|
||||
emitValue([...internalValue.value, newDefaults]);
|
||||
} else {
|
||||
if (props.value != null) {
|
||||
if (internalValue.value != null) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
'The repeater interface expects an array as value, but the given value is no array. Overriding given value.'
|
||||
@@ -277,7 +296,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
edits.value = { ...newDefaults };
|
||||
active.value = (props.value || []).length;
|
||||
active.value = (internalValue.value || []).length;
|
||||
}
|
||||
|
||||
function emitValue(value: null | any[]) {
|
||||
@@ -298,7 +317,7 @@ export default defineComponent({
|
||||
|
||||
function closeDrawer() {
|
||||
if (isNewItem.value) {
|
||||
emitValue(props.value.slice(0, -1));
|
||||
emitValue(internalValue.value.slice(0, -1));
|
||||
}
|
||||
|
||||
edits.value = {};
|
||||
|
||||
@@ -14,6 +14,16 @@
|
||||
@input="addLabel = $event"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid-element half-left">
|
||||
<p class="type-label">{{ t('interfaces.list.sort') }}</p>
|
||||
<v-select
|
||||
v-model="sort"
|
||||
class="input"
|
||||
:items="sortFields"
|
||||
show-deselect
|
||||
:placeholder="t('interfaces.list.sort_placeholder')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid-element full">
|
||||
<p class="type-label">{{ t('interfaces.list.edit_fields') }}</p>
|
||||
@@ -188,7 +198,27 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
|
||||
return { t, repeaterValue, repeaterFields, template, addLabel };
|
||||
const sort = computed({
|
||||
get() {
|
||||
return props.value?.sort;
|
||||
},
|
||||
set(newSort: string) {
|
||||
emit('input', {
|
||||
...(props.value || {}),
|
||||
sort: newSort,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const sortFields = computed(() => {
|
||||
if (!repeaterValue.value) return [];
|
||||
|
||||
return repeaterValue.value.map((val) => {
|
||||
return { text: val.field, value: val.field };
|
||||
});
|
||||
});
|
||||
|
||||
return { t, repeaterValue, repeaterFields, template, addLabel, sort, sortFields };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1613,6 +1613,8 @@ interfaces:
|
||||
description: Create multiple entries of the same structure
|
||||
edit_fields: Edit Fields
|
||||
add_label: '"Create New" Label'
|
||||
sort: Sort
|
||||
sort_placeholder: Manual
|
||||
field_name_placeholder: Enter field name...
|
||||
field_note_placeholder: Enter field note...
|
||||
incompatible_data:
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { ref, computed, watch, toRefs } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { isEqual } from 'lodash';
|
||||
import { Field, DeepPartial } from '@directus/shared/types';
|
||||
@@ -64,6 +64,8 @@ const emit = defineEmits(['update:modelValue', 'savedKey']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { translationString } = toRefs(props);
|
||||
|
||||
const confirmDelete = ref<boolean>(false);
|
||||
|
||||
const values = ref<TranslationString>({
|
||||
@@ -79,7 +81,7 @@ const formValues = computed<TranslationString>({
|
||||
values.value.key = val.key;
|
||||
|
||||
if (!val.translations) {
|
||||
values.value.translations = null;
|
||||
values.value.translations = val.translations;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -121,6 +123,7 @@ const fields = computed<DeepPartial<Field>[]>(() => {
|
||||
options: {
|
||||
placeholder: '$t:translation_string_translations_placeholder',
|
||||
template: '{{ language }} {{ translation }}',
|
||||
sort: 'language',
|
||||
fields: [
|
||||
{
|
||||
field: 'language',
|
||||
@@ -159,8 +162,8 @@ const fields = computed<DeepPartial<Field>[]>(() => {
|
||||
const { translationStrings, updating, update } = useTranslationStrings();
|
||||
|
||||
watch(
|
||||
() => props.translationString,
|
||||
(newVal: TranslationString) => {
|
||||
translationString,
|
||||
(newVal: TranslationString | null) => {
|
||||
values.value.key = newVal?.key ?? null;
|
||||
values.value.translations = newVal?.translations ?? null;
|
||||
initialValues.value.key = newVal?.key ?? null;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<ValueNull v-if="!displayedTranslation && !hideDisplayText" />
|
||||
<div v-else class="translation-strings-display">
|
||||
<span v-if="!hideDisplayText" class="translation-display-text">{{ displayedTranslation.translation }}</span>
|
||||
<v-menu class="menu" show-arrow :disabled="!translations || translations.length === 0">
|
||||
<span v-if="!hideDisplayText" class="translation-display-text">{{ displayedTranslation!.translation }}</span>
|
||||
<v-menu class="menu" show-arrow :disabled="!sortedTranslations || sortedTranslations.length === 0">
|
||||
<template #activator="{ toggle, deactivate, active }">
|
||||
<v-icon
|
||||
v-tooltip.bottom="translations && translations.length === 0 && t('no_translations')"
|
||||
v-tooltip.bottom="sortedTranslations && sortedTranslations.length === 0 && t('no_translations')"
|
||||
:small="!hideDisplayText"
|
||||
class="icon"
|
||||
:class="{ active }"
|
||||
@@ -17,7 +17,7 @@
|
||||
</template>
|
||||
|
||||
<v-list class="translations">
|
||||
<v-list-item v-for="item in translations" :key="item.language">
|
||||
<v-list-item v-for="item in sortedTranslations" :key="item.language">
|
||||
<v-list-item-content>
|
||||
<div class="header">
|
||||
<div class="lang">
|
||||
@@ -40,6 +40,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import { useUserStore } from '@/stores';
|
||||
import ValueNull from '@/views/private/components/value-null';
|
||||
import { TranslationString } from '@/composables/use-translation-strings';
|
||||
import { sortBy } from 'lodash';
|
||||
|
||||
interface Props {
|
||||
translations?: TranslationString['translations'];
|
||||
@@ -48,18 +49,23 @@ interface Props {
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), { translations: () => null, hideDisplayText: false });
|
||||
|
||||
const sortedTranslations = computed(() => {
|
||||
if (!props.translations) return [];
|
||||
return sortBy(props.translations, ['language']);
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { currentUser } = useUserStore();
|
||||
|
||||
const displayedTranslation = computed(() => {
|
||||
if (!props.translations || props.translations.length === 0) return null;
|
||||
if (!sortedTranslations.value || sortedTranslations.value.length === 0) return null;
|
||||
|
||||
if (currentUser && 'id' in currentUser) {
|
||||
return props.translations.find((val) => val.language === currentUser.language) ?? props.translations[0];
|
||||
return sortedTranslations.value.find((val) => val.language === currentUser.language) ?? sortedTranslations.value[0];
|
||||
}
|
||||
|
||||
return props.translations[0];
|
||||
return sortedTranslations.value[0];
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user